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.
- package/android/src/main/java/com/rnsnativecall/CallHeadlessTask.kt +36 -6
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +37 -40
- package/app.plugin.js +2 -9
- package/package.json +1 -3
- package/withNativeCallVoip.js +51 -108
- package/withCallNativeConfig.js +0 -147
- package/withCallPermissions.js +0 -41
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 {
|
|
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)
|
|
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
|
|
8
|
-
return
|
|
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.
|
|
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": {
|
package/withNativeCallVoip.js
CHANGED
|
@@ -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
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
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\(.*\)/,
|
|
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
|
|
110
|
-
const
|
|
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
|
-
//
|
|
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',
|
|
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.
|
|
74
|
+
'android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS',
|
|
75
|
+
'android.permission.RECEIVE_BOOT_COMPLETED'
|
|
122
76
|
];
|
|
123
77
|
|
|
124
|
-
|
|
78
|
+
androidManifest['uses-permission'] = androidManifest['uses-permission'] || [];
|
|
125
79
|
permissions.forEach((perm) => {
|
|
126
|
-
if (!
|
|
127
|
-
|
|
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
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
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':
|
|
90
|
+
'android:name': 'com.rnsnativecall.AcceptCallActivity',
|
|
91
|
+
'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
|
|
158
92
|
'android:exported': 'false',
|
|
159
|
-
|
|
160
|
-
'android:
|
|
93
|
+
'android:showWhenLocked': 'true',
|
|
94
|
+
'android:turnScreenOn': 'true'
|
|
161
95
|
}
|
|
162
96
|
});
|
|
163
97
|
}
|
|
164
98
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!
|
|
169
|
-
|
|
170
|
-
$: { 'android:name':
|
|
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;
|
package/withCallNativeConfig.js
DELETED
|
@@ -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
|
-
};
|
package/withCallPermissions.js
DELETED
|
@@ -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
|
-
};
|