rns-nativecall 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,53 @@
1
1
  package com.rnsnativecall
2
2
 
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.content.Context
3
7
  import android.content.Intent
8
+ import android.content.pm.ServiceInfo
9
+ import android.os.Build
10
+ import androidx.core.app.NotificationCompat
4
11
  import com.facebook.react.HeadlessJsTaskService
5
12
  import com.facebook.react.bridge.Arguments
6
13
  import com.facebook.react.jstasks.HeadlessJsTaskConfig
14
+ import com.facebook.react.jstasks.LinearCountingRetryPolicy
7
15
 
8
16
  class CallHeadlessTask : HeadlessJsTaskService() {
9
- // Note: The '?' after Intent and the return type are specific in Kotlin
17
+
18
+ override fun onCreate() {
19
+ super.onCreate()
20
+ val channelId = "call_service"
21
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
22
+ val channel = NotificationChannel(channelId, "Call Service", NotificationManager.IMPORTANCE_LOW)
23
+ val manager = getSystemService(NotificationManager::class.java)
24
+ manager.createNotificationChannel(channel)
25
+ }
26
+
27
+ val notification: Notification = NotificationCompat.Builder(this, channelId)
28
+ .setContentTitle("Incoming Call")
29
+ .setSmallIcon(android.R.drawable.sym_call_incoming)
30
+ .setPriority(NotificationCompat.PRIORITY_LOW)
31
+ .build()
32
+
33
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
34
+ startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL)
35
+ } else {
36
+ startForeground(1, notification)
37
+ }
38
+ }
39
+
10
40
  override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
11
41
  val extras = intent?.extras
12
42
  return if (extras != null) {
43
+ val retryPolicy = LinearCountingRetryPolicy(3, 1000)
13
44
  HeadlessJsTaskConfig(
14
45
  "ColdStartCallTask",
15
46
  Arguments.fromBundle(extras),
16
- 30000,
17
- true
47
+ 60000,
48
+ true,
49
+ retryPolicy
18
50
  )
19
- } else {
20
- null
21
- }
51
+ } else null
22
52
  }
23
53
  }
@@ -29,47 +29,47 @@ class CallMessagingService : FirebaseMessagingService() {
29
29
  val type = data["type"] ?: ""
30
30
 
31
31
  if (type == "CANCEL") {
32
- pendingNotifications[uuid]?.let {
33
- handler.removeCallbacks(it)
34
- pendingNotifications.remove(uuid)
35
- }
36
-
37
- // Pass the uuid so the manager knows exactly which notification to remove
38
- NativeCallManager.dismissIncomingCall(context, uuid)
39
-
40
- showMissedCallNotification(context, data, uuid)
41
- return
42
- }
32
+ pendingNotifications[uuid]?.let {
33
+ handler.removeCallbacks(it)
34
+ pendingNotifications.remove(uuid)
35
+ }
36
+ NativeCallManager.dismissIncomingCall(context, uuid)
37
+ showMissedCallNotification(context, data, uuid)
38
+ return
39
+ }
43
40
 
44
41
  if (isAppInForeground(context)) {
45
42
  CallModule.sendEventToJS("onCallReceived", data)
46
43
  } else {
47
- // 1. Prepare the Intent
48
- val headlessIntent = Intent(context, CallHeadlessTask::class.java)
49
- val bundle = Bundle()
50
- data.forEach { (k, v) -> bundle.putString(k, v) }
51
- headlessIntent.putExtras(bundle)
52
-
53
- // 2. Start the Service
54
- context.startService(headlessIntent)
55
-
56
- // 3. Acquire WakeLock to prevent the CPU from sleeping during bundling
57
- try {
58
- HeadlessJsTaskService.acquireWakeLockNow(context)
59
- } catch (e: Exception) {
60
- e.printStackTrace()
61
- }
44
+ // 1. Prepare Intent
45
+ val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
46
+ putExtras(Bundle().apply {
47
+ data.forEach { (k, v) -> putString(k, v) }
48
+ })
49
+ }
62
50
 
63
- // 4. Start the 18s backup timer
64
- val showNotificationRunnable = Runnable {
65
- if (!isAppInForeground(context)) {
66
- NativeCallManager.handleIncomingPush(context, data)
51
+ // 2. Start Service correctly based on SDK
52
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
53
+ context.startForegroundService(headlessIntent)
54
+ } else {
55
+ context.startService(headlessIntent)
56
+ }
57
+
58
+ // 3. WakeLock
59
+ try {
60
+ HeadlessJsTaskService.acquireWakeLockNow(context)
61
+ } catch (e: Exception) { e.printStackTrace() }
62
+
63
+ // 4. Backup Timer
64
+ val showNotificationRunnable = Runnable {
65
+ if (!isAppInForeground(context)) {
66
+ NativeCallManager.handleIncomingPush(context, data)
67
+ }
68
+ pendingNotifications.remove(uuid)
69
+ }
70
+ pendingNotifications[uuid] = showNotificationRunnable
71
+ handler.postDelayed(showNotificationRunnable, 18000)
67
72
  }
68
- pendingNotifications.remove(uuid)
69
- }
70
- pendingNotifications[uuid] = showNotificationRunnable
71
- handler.postDelayed(showNotificationRunnable, 18000)
72
- }
73
73
  }
74
74
 
75
75
  private fun showMissedCallNotification(context: Context, data: Map<String, String>, uuid: String) {
@@ -77,9 +77,8 @@ class CallMessagingService : FirebaseMessagingService() {
77
77
  val callType = data["callType"] ?: "video"
78
78
  val channelId = "missed_calls"
79
79
  val article = if (callType.startsWith("a", ignoreCase = true)) "an" else "a"
80
- // 1. Format App Name: Capitalized
81
80
  val appName = context.applicationInfo.loadLabel(context.packageManager).toString()
82
- val capitalizedAppName = appName.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
81
+ val capitalizedAppName = appName.replaceFirstChar { it.uppercase() }
83
82
 
84
83
  val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
85
84
 
@@ -88,7 +87,6 @@ class CallMessagingService : FirebaseMessagingService() {
88
87
  notificationManager.createNotificationChannel(channel)
89
88
  }
90
89
 
91
- // 2. Create Intent to open app when clicking missed call
92
90
  val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
93
91
  val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
94
92
  PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
@@ -106,10 +104,9 @@ class CallMessagingService : FirebaseMessagingService() {
106
104
  .setContentText("You missed $article $callType call from $name")
107
105
  .setPriority(NotificationCompat.PRIORITY_HIGH)
108
106
  .setAutoCancel(true)
109
- .setContentIntent(contentIntent) // Opens app on click
107
+ .setContentIntent(contentIntent)
110
108
  .setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
111
109
 
112
- // 3. Use unique hashCode so multiple missed calls are stacked separately
113
110
  notificationManager.notify(uuid.hashCode(), builder.build())
114
111
  }
115
112
 
package/app.plugin.js CHANGED
@@ -1,13 +1,6 @@
1
1
  ///app.plugin.js
2
- const { withPlugins } = require('@expo/config-plugins');
3
2
  const withNativeCallVoip = require('./withNativeCallVoip');
4
- const withCallNativeConfig = require('./withCallNativeConfig');
5
- const withCallPermissions = require('./withCallPermissions');
6
3
 
7
- module.exports = function (config, props) {
8
- return withPlugins(config, [
9
- [withNativeCallVoip, props],
10
- [withCallNativeConfig, props],
11
- [withCallPermissions, props],
12
- ]);
4
+ module.exports = function (config) {
5
+ return withNativeCallVoip(config);
13
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "RNS nativecall component with native Android/iOS for handling native call ui, when app is not open or open.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -43,8 +43,6 @@
43
43
  "react-native.config.js",
44
44
  "README.md",
45
45
  "rns-nativecall.podspec",
46
- "withCallNativeConfig.js",
47
- "withCallPermissions.js",
48
46
  "withNativeCallVoip.js"
49
47
  ],
50
48
  "peerDependencies": {
@@ -1,76 +1,26 @@
1
1
  const { withAndroidManifest, withInfoPlist, withPlugins, withMainActivity } = require('@expo/config-plugins');
2
2
 
3
- /** 1. ANDROID MAIN ACTIVITY MOD **/
4
- // function withMainActivityDataFix(config) {
5
- // return withMainActivity(config, (config) => {
6
- // let contents = config.modResults.contents;
7
-
8
- // // 1. Ensure necessary Imports are present
9
- // if (!contents.includes('import android.content.Intent')) {
10
- // contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
11
- // }
12
- // if (!contents.includes('import android.os.Bundle')) {
13
- // contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.os.Bundle`);
14
- // }
15
-
16
- // // 2. Define the code blocks
17
- // const onNewIntentCode = `
18
- // override fun onNewIntent(intent: Intent) {
19
- // super.onNewIntent(intent)
20
- // setIntent(intent)
21
- // }
22
- // `;
23
-
24
- // const onCreateCode = `
25
- // override fun onCreate(savedInstanceState: Bundle?) {
26
- // super.onCreate(savedInstanceState)
27
- // // If woken up silently, move to back to prevent UI flicker
28
- // if (intent.getBooleanExtra("background_wake", false)) {
29
- // moveTaskToBack(true)
30
- // }
31
- // }
32
- // `;
33
-
34
- // // 3. Insert the codes before the last closing brace of the class
35
- // if (!contents.includes('override fun onNewIntent')) {
36
- // const lastBraceIndex = contents.lastIndexOf('}');
37
- // contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
38
- // }
39
-
40
- // if (!contents.includes('override fun onCreate')) {
41
- // // Re-calculate lastBraceIndex because contents string has changed
42
- // const lastBraceIndex = contents.lastIndexOf('}');
43
- // contents = contents.slice(0, lastBraceIndex) + onCreateCode + contents.slice(lastBraceIndex);
44
- // }
45
-
46
- // config.modResults.contents = contents;
47
- // return config;
48
- // });
49
- // }
50
-
51
3
  /** 1. ANDROID MAIN ACTIVITY MOD **/
52
4
  function withMainActivityDataFix(config) {
53
5
  return withMainActivity(config, (config) => {
54
6
  let contents = config.modResults.contents;
55
7
 
56
- // 1. Add necessary Imports safely
57
8
  const imports = [
58
9
  'import android.view.WindowManager',
59
10
  'import android.os.Build',
60
11
  'import android.os.Bundle',
61
12
  'import android.content.Intent'
62
13
  ];
14
+
63
15
  imports.forEach(imp => {
64
16
  if (!contents.includes(imp)) {
65
17
  contents = contents.replace(/package .*/, (match) => `${match}\n${imp}`);
66
18
  }
67
19
  });
68
20
 
69
- // 2. Inject onCreate logic without breaking the existing structure
70
- // We look for super.onCreate(null) or super.onCreate(savedInstanceState)
71
- const wakeLogig = `
72
- super.onCreate(savedInstanceState)
73
- if (intent?.action?.startsWith("ACTION_SHOW_UI") == true || intent?.getBooleanExtra("background_wake", false) == true) {
21
+ // FIXED: Corrected Kotlin bitwise OR syntax and logic check
22
+ const wakeLogic = `super.onCreate(savedInstanceState)
23
+ if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
74
24
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
75
25
  setShowWhenLocked(true)
76
26
  setTurnScreenOn(true)
@@ -80,20 +30,15 @@ function withMainActivityDataFix(config) {
80
30
  }`;
81
31
 
82
32
  if (!contents.includes('setShowWhenLocked')) {
83
- contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogig);
33
+ contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogic);
84
34
  }
85
35
 
86
- // 3. REMOVE the "Update createReactActivityDelegate" section entirely.
87
- // Modern Expo handles the Wrapper correctly; your regex was breaking the Kotlin syntax.
88
-
89
- // 4. Ensure onNewIntent exists
90
36
  if (!contents.includes('override fun onNewIntent')) {
91
37
  const onNewIntentCode = `
92
38
  override fun onNewIntent(intent: Intent) {
93
39
  super.onNewIntent(intent)
94
40
  setIntent(intent)
95
41
  }\n`;
96
- // Insert it before the last closing brace of the class
97
42
  const lastBraceIndex = contents.lastIndexOf('}');
98
43
  contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
99
44
  }
@@ -106,81 +51,79 @@ function withMainActivityDataFix(config) {
106
51
  /** 2. ANDROID MANIFEST CONFIG **/
107
52
  function withAndroidConfig(config) {
108
53
  return withAndroidManifest(config, (config) => {
109
- const manifest = config.modResults;
110
- const application = manifest.manifest.application[0];
54
+ const androidManifest = config.modResults.manifest;
55
+ const mainApplication = androidManifest.application[0];
56
+
57
+ // 1. MainActivity flags
58
+ const mainActivity = mainApplication.activity.find(a => a.$["android:name"] === ".MainActivity");
59
+ if (mainActivity) {
60
+ mainActivity.$["android:showWhenLocked"] = "true";
61
+ mainActivity.$["android:turnScreenOn"] = "true";
62
+ }
111
63
 
112
- // 1. Unified Permissions
64
+ // 2. Permissions
113
65
  const permissions = [
114
66
  'android.permission.USE_FULL_SCREEN_INTENT',
115
67
  'android.permission.VIBRATE',
116
68
  'android.permission.FOREGROUND_SERVICE',
117
- 'android.permission.FOREGROUND_SERVICE_PHONE_CALL', // Required for Android 14
69
+ 'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
118
70
  'android.permission.POST_NOTIFICATIONS',
119
71
  'android.permission.WAKE_LOCK',
72
+ 'android.permission.MANAGE_OWN_CALLS',
120
73
  'android.permission.DISABLE_KEYGUARD',
121
- 'android.permission.RECEIVE_BOOT_COMPLETED' // Allows app to wake after phone restart
74
+ 'android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS',
75
+ 'android.permission.RECEIVE_BOOT_COMPLETED'
122
76
  ];
123
77
 
124
- manifest.manifest['uses-permission'] = manifest.manifest['uses-permission'] || [];
78
+ androidManifest['uses-permission'] = androidManifest['uses-permission'] || [];
125
79
  permissions.forEach((perm) => {
126
- if (!manifest.manifest['uses-permission'].some((p) => p.$['android:name'] === perm)) {
127
- manifest.manifest['uses-permission'].push({ $: { 'android:name': perm } });
80
+ if (!androidManifest['uses-permission'].some((p) => p.$['android:name'] === perm)) {
81
+ androidManifest['uses-permission'].push({ $: { 'android:name': perm } });
128
82
  }
129
83
  });
130
84
 
131
- // 2. Activity Setup (Remain unchanged as your logic was correct)
132
- application.activity = application.activity || [];
133
- // ... (Keep your AcceptCallActivity logic here)
134
-
135
- // 3. Service Setup (CRITICAL UPDATES FOR ANDROID 14)
136
- application.service = application.service || [];
137
-
138
- // Add Firebase Messaging Service
139
- const firebaseServiceName = 'com.rnsnativecall.CallMessagingService';
140
- if (!application.service.some(s => s.$['android:name'] === firebaseServiceName)) {
141
- application.service.push({
142
- $: {
143
- 'android:name': firebaseServiceName,
144
- 'android:exported': 'false',
145
- // Adding type for Android 14 stability
146
- 'android:foregroundServiceType': 'phoneCall'
147
- },
148
- 'intent-filter': [{ action: [{ $: { 'android:name': 'com.google.firebase.MESSAGING_EVENT' } }] }]
149
- });
150
- }
151
-
152
- // Add Headless JS Task Service
153
- const headlessServiceName = 'com.rnsnativecall.CallHeadlessTask';
154
- if (!application.service.some(s => s.$['android:name'] === headlessServiceName)) {
155
- application.service.push({
85
+ // 3. Activity/Service/Receiver registration (Cleaned up duplicates)
86
+ mainApplication.activity = mainApplication.activity || [];
87
+ if (!mainApplication.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
88
+ mainApplication.activity.push({
156
89
  $: {
157
- 'android:name': headlessServiceName,
90
+ 'android:name': 'com.rnsnativecall.AcceptCallActivity',
91
+ 'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
158
92
  'android:exported': 'false',
159
- // This ensures the JS bridge is treated as a priority process
160
- 'android:foregroundServiceType': 'phoneCall'
93
+ 'android:showWhenLocked': 'true',
94
+ 'android:turnScreenOn': 'true'
161
95
  }
162
96
  });
163
97
  }
164
98
 
165
- // 4. Receiver Setup
166
- application.receiver = application.receiver || [];
167
- const receiverName = 'com.rnsnativecall.CallActionReceiver';
168
- if (!application.receiver.some(r => r.$['android:name'] === receiverName)) {
169
- application.receiver.push({
170
- $: { 'android:name': receiverName, 'android:exported': 'false' },
171
- 'intent-filter': [{
172
- action: [
173
- { $: { 'android:name': 'android.intent.action.BOOT_COMPLETED' } }
174
- ]
175
- }]
99
+ mainApplication.service = mainApplication.service || [];
100
+
101
+ // Messaging Service
102
+ if (!mainApplication.service.some(s => s.$['android:name'] === 'com.rnsnativecall.CallMessagingService')) {
103
+ mainApplication.service.push({
104
+ $: { 'android:name': 'com.rnsnativecall.CallMessagingService', 'android:exported': 'false' },
105
+ 'intent-filter': [{ action: [{ $: { 'android:name': 'com.google.firebase.MESSAGING_EVENT' } }] }]
176
106
  });
177
107
  }
178
108
 
109
+ // Headless Task Service (Fixed for Android 14)
110
+ const headlessName = 'com.rnsnativecall.CallHeadlessTask';
111
+ const headlessIndex = mainApplication.service.findIndex(s => s.$['android:name'] === headlessName);
112
+ const headlessEntry = {
113
+ $: {
114
+ 'android:name': headlessName,
115
+ 'android:exported': 'false',
116
+ 'android:foregroundServiceType': 'phoneCall'
117
+ }
118
+ };
119
+
120
+ if (headlessIndex > -1) mainApplication.service[headlessIndex] = headlessEntry;
121
+ else mainApplication.service.push(headlessEntry);
122
+
179
123
  return config;
180
124
  });
181
125
  }
182
126
 
183
- /** 3. IOS CONFIG **/
184
127
  function withIosConfig(config) {
185
128
  return withInfoPlist(config, (config) => {
186
129
  const infoPlist = config.modResults;
@@ -1,147 +0,0 @@
1
- const { withAndroidManifest, withInfoPlist, withPlugins, withMainActivity } = require('@expo/config-plugins');
2
-
3
- /** 1. ANDROID MAIN ACTIVITY MODS **/
4
- function withMainActivityDataFix(config) {
5
- return withMainActivity(config, (config) => {
6
- let contents = config.modResults.contents;
7
-
8
- // Ensure necessary Imports
9
- if (!contents.includes('import android.content.Intent')) {
10
- contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
11
- }
12
- if (!contents.includes('import android.os.Bundle')) {
13
- contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.os.Bundle`);
14
- }
15
-
16
- const onNewIntentCode = `
17
- override fun onNewIntent(intent: Intent) {
18
- super.onNewIntent(intent)
19
- setIntent(intent)
20
- }
21
- `;
22
-
23
- const onCreateCode = `
24
- override fun onCreate(savedInstanceState: Bundle?) {
25
- super.onCreate(savedInstanceState)
26
- // If woken up by a background task, push to back immediately
27
- if (intent.getBooleanExtra("background_wake", false)) {
28
- moveTaskToBack(true)
29
- }
30
- }
31
- `;
32
-
33
- // Insertion logic
34
- if (!contents.includes('override fun onNewIntent')) {
35
- contents = contents.replace(/class MainActivity : .*/, (match) => `${match}\n${onNewIntentCode}`);
36
- }
37
-
38
- if (!contents.includes('override fun onCreate')) {
39
- // Find the end of the class or after onNewIntent
40
- const lastBraceIndex = contents.lastIndexOf('}');
41
- contents = contents.slice(0, lastBraceIndex) + onCreateCode + contents.slice(lastBraceIndex);
42
- }
43
-
44
- config.modResults.contents = contents;
45
- return config;
46
- });
47
- }
48
-
49
- /** 2. ANDROID MANIFEST CONFIG **/
50
- function withAndroidConfig(config) {
51
- return withAndroidManifest(config, (config) => {
52
- const manifest = config.modResults.manifest;
53
- const application = manifest.application[0];
54
-
55
- // 1. Permissions (Updated for Android 14 compatibility)
56
- const permissions = [
57
- 'android.permission.USE_FULL_SCREEN_INTENT',
58
- 'android.permission.VIBRATE',
59
- 'android.permission.FOREGROUND_SERVICE',
60
- 'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
61
- 'android.permission.POST_NOTIFICATIONS',
62
- 'android.permission.WAKE_LOCK',
63
- 'android.permission.DISABLE_KEYGUARD',
64
- 'android.permission.RECEIVE_BOOT_COMPLETED'
65
- ];
66
-
67
- manifest['uses-permission'] = manifest['uses-permission'] || [];
68
- permissions.forEach((perm) => {
69
- if (!manifest['uses-permission'].some((p) => p.$['android:name'] === perm)) {
70
- manifest['uses-permission'].push({ $: { 'android:name': perm } });
71
- }
72
- });
73
-
74
- // 2. Activity Setup (AcceptCallActivity)
75
- application.activity = application.activity || [];
76
- if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
77
- application.activity.push({
78
- $: {
79
- 'android:name': 'com.rnsnativecall.AcceptCallActivity',
80
- 'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
81
- 'android:excludeFromRecents': 'true',
82
- 'android:noHistory': 'true',
83
- 'android:exported': 'false',
84
- 'android:launchMode': 'singleInstance',
85
- 'android:showWhenLocked': 'true',
86
- 'android:turnScreenOn': 'true'
87
- }
88
- });
89
- }
90
-
91
- // 3. Service Registration (The Headless Enforcer)
92
- application.service = application.service || [];
93
-
94
- // Firebase Messaging Service
95
- if (!application.service.some(s => s.$['android:name'] === 'com.rnsnativecall.CallMessagingService')) {
96
- application.service.push({
97
- $: {
98
- 'android:name': 'com.rnsnativecall.CallMessagingService',
99
- 'android:exported': 'false'
100
- },
101
- 'intent-filter': [{
102
- action: [{ $: { 'android:name': 'com.google.firebase.MESSAGING_EVENT' } }]
103
- }]
104
- });
105
- }
106
-
107
- // Headless Task Service
108
- if (!application.service.some(s => s.$['android:name'] === 'com.rnsnativecall.CallHeadlessTask')) {
109
- application.service.push({
110
- $: {
111
- 'android:name': 'com.rnsnativecall.CallHeadlessTask',
112
- 'android:exported': 'false' // Headless JS should be internal only
113
- }
114
- });
115
- }
116
-
117
- // 4. Action Receiver
118
- application.receiver = application.receiver || [];
119
- if (!application.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
120
- application.receiver.push({
121
- $: { 'android:name': 'com.rnsnativecall.CallActionReceiver', 'android:exported': 'false' }
122
- });
123
- }
124
-
125
- return config;
126
- });
127
- }
128
-
129
- /** 3. IOS CONFIG **/
130
- function withIosConfig(config) {
131
- return withInfoPlist(config, (config) => {
132
- const infoPlist = config.modResults;
133
- if (!infoPlist.UIBackgroundModes) infoPlist.UIBackgroundModes = [];
134
- ['voip', 'audio', 'remote-notification'].forEach(mode => {
135
- if (!infoPlist.UIBackgroundModes.includes(mode)) infoPlist.UIBackgroundModes.push(mode);
136
- });
137
- return config;
138
- });
139
- }
140
-
141
- module.exports = (config) => {
142
- return withPlugins(config, [
143
- withAndroidConfig,
144
- withMainActivityDataFix,
145
- withIosConfig
146
- ]);
147
- };
@@ -1,41 +0,0 @@
1
- const { withPlugins, withAndroidManifest } = require('@expo/config-plugins'); // Add this!
2
-
3
-
4
- /** 2. ANDROID MANIFEST MOD **/
5
- function withCallPermissions(config) {
6
- return withAndroidManifest(config, async (config) => {
7
- const androidManifest = config.modResults.manifest;
8
-
9
- // 1. Add the Full Screen Intent Permission
10
- if (!androidManifest["uses-permission"]) {
11
- androidManifest["uses-permission"] = [];
12
- }
13
-
14
- const hasPermission = androidManifest["uses-permission"].some(
15
- (p) => p.$["android:name"] === "android.permission.USE_FULL_SCREEN_INTENT"
16
- );
17
-
18
- if (!hasPermission) {
19
- androidManifest["uses-permission"].push({
20
- $: { "android:name": "android.permission.USE_FULL_SCREEN_INTENT" },
21
- });
22
- }
23
-
24
- // 2. Ensure the Activity has the correct flags to appear over lockscreen
25
- const mainApplication = androidManifest.application[0];
26
- const mainActivity = mainApplication.activity.find(
27
- (a) => a.$["android:name"] === ".MainActivity"
28
- );
29
-
30
- if (mainActivity) {
31
- mainActivity.$["android:showOnLockScreen"] = "true";
32
- mainActivity.$["android:lockTaskMode"] = "if_whitelisted";
33
- }
34
-
35
- return config;
36
- });
37
- }
38
-
39
- module.exports = (config) => {
40
- return withPlugins(config, [withCallPermissions]);
41
- };