rns-nativecall 0.4.2 → 0.4.4
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/build.gradle +3 -0
- package/android/src/main/java/com/rnsnativecall/CallHeadlessTask.kt +36 -6
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +37 -40
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +9 -4
- package/package.json +1 -3
- package/withNativeCallVoip.js +25 -38
- package/withCallNativeConfig.js +0 -148
- package/withCallPermissions.js +0 -42
package/android/build.gradle
CHANGED
|
@@ -68,4 +68,7 @@ dependencies {
|
|
|
68
68
|
// Glide for profile pictures
|
|
69
69
|
implementation "com.github.bumptech.glide:glide:4.15.1"
|
|
70
70
|
annotationProcessor "com.github.bumptech.glide:compiler:4.15.1"
|
|
71
|
+
|
|
72
|
+
implementation "androidx.core:core-ktx:1.12.0"
|
|
73
|
+
implementation "androidx.appcompat:appcompat:1.6.1"
|
|
71
74
|
}
|
|
@@ -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("")
|
|
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
|
|
|
@@ -11,6 +11,9 @@ import android.media.Ringtone
|
|
|
11
11
|
import android.media.RingtoneManager
|
|
12
12
|
import android.graphics.Color
|
|
13
13
|
|
|
14
|
+
import androidx.core.app.Person
|
|
15
|
+
import androidx.core.app.NotificationCompat.CallStyle
|
|
16
|
+
|
|
14
17
|
object NativeCallManager {
|
|
15
18
|
|
|
16
19
|
private var ringtone: Ringtone? = null
|
|
@@ -20,7 +23,7 @@ object NativeCallManager {
|
|
|
20
23
|
val uuid = data["callUuid"] ?: return
|
|
21
24
|
stopRingtone()
|
|
22
25
|
|
|
23
|
-
val name = data["name"] ?: "
|
|
26
|
+
val name = data["name"] ?: "Someone"
|
|
24
27
|
val callType = data["callType"] ?: "audio"
|
|
25
28
|
|
|
26
29
|
val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
@@ -52,7 +55,7 @@ object NativeCallManager {
|
|
|
52
55
|
val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Call", NotificationManager.IMPORTANCE_HIGH).apply {
|
|
53
56
|
setBypassDnd(true)
|
|
54
57
|
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
|
55
|
-
enableVibration(
|
|
58
|
+
enableVibration(false) //can be removed
|
|
56
59
|
setSound(null, null)
|
|
57
60
|
}
|
|
58
61
|
notificationManager.createNotificationChannel(channel)
|
|
@@ -71,8 +74,10 @@ object NativeCallManager {
|
|
|
71
74
|
.setFullScreenIntent(fullScreenPendingIntent, false) //can be true
|
|
72
75
|
.addAction(0, "Answer", fullScreenPendingIntent)
|
|
73
76
|
.addAction(0, "Decline", rejectPendingIntent)
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
.setColor(Color.parseColor("#28a745")) // Green color for the "Pill"
|
|
78
|
+
.setColorized(true)
|
|
79
|
+
|
|
80
|
+
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
76
81
|
|
|
77
82
|
try {
|
|
78
83
|
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rns-nativecall",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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
|
@@ -5,20 +5,20 @@ function withMainActivityDataFix(config) {
|
|
|
5
5
|
return withMainActivity(config, (config) => {
|
|
6
6
|
let contents = config.modResults.contents;
|
|
7
7
|
|
|
8
|
-
// 1. Add required imports
|
|
9
8
|
const imports = [
|
|
10
9
|
'import android.view.WindowManager',
|
|
11
10
|
'import android.os.Build',
|
|
12
11
|
'import android.os.Bundle',
|
|
13
12
|
'import android.content.Intent'
|
|
14
13
|
];
|
|
14
|
+
|
|
15
15
|
imports.forEach(imp => {
|
|
16
16
|
if (!contents.includes(imp)) {
|
|
17
17
|
contents = contents.replace(/package .*/, (match) => `${match}\n${imp}`);
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// FIXED: Corrected Kotlin bitwise OR syntax and logic check
|
|
22
22
|
const wakeLogic = `super.onCreate(savedInstanceState)
|
|
23
23
|
if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
|
|
24
24
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
@@ -33,7 +33,6 @@ function withMainActivityDataFix(config) {
|
|
|
33
33
|
contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogic);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
// 3. Ensure onNewIntent is present
|
|
37
36
|
if (!contents.includes('override fun onNewIntent')) {
|
|
38
37
|
const onNewIntentCode = `
|
|
39
38
|
override fun onNewIntent(intent: Intent) {
|
|
@@ -55,14 +54,14 @@ function withAndroidConfig(config) {
|
|
|
55
54
|
const androidManifest = config.modResults.manifest;
|
|
56
55
|
const mainApplication = androidManifest.application[0];
|
|
57
56
|
|
|
58
|
-
// 1.
|
|
57
|
+
// 1. MainActivity flags
|
|
59
58
|
const mainActivity = mainApplication.activity.find(a => a.$["android:name"] === ".MainActivity");
|
|
60
59
|
if (mainActivity) {
|
|
61
60
|
mainActivity.$["android:showWhenLocked"] = "true";
|
|
62
61
|
mainActivity.$["android:turnScreenOn"] = "true";
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
// 2.
|
|
64
|
+
// 2. Permissions
|
|
66
65
|
const permissions = [
|
|
67
66
|
'android.permission.USE_FULL_SCREEN_INTENT',
|
|
68
67
|
'android.permission.VIBRATE',
|
|
@@ -70,7 +69,9 @@ function withAndroidConfig(config) {
|
|
|
70
69
|
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
|
|
71
70
|
'android.permission.POST_NOTIFICATIONS',
|
|
72
71
|
'android.permission.WAKE_LOCK',
|
|
72
|
+
'android.permission.MANAGE_OWN_CALLS',
|
|
73
73
|
'android.permission.DISABLE_KEYGUARD',
|
|
74
|
+
'android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS',
|
|
74
75
|
'android.permission.RECEIVE_BOOT_COMPLETED'
|
|
75
76
|
];
|
|
76
77
|
|
|
@@ -81,7 +82,7 @@ function withAndroidConfig(config) {
|
|
|
81
82
|
}
|
|
82
83
|
});
|
|
83
84
|
|
|
84
|
-
// 3.
|
|
85
|
+
// 3. Activity/Service/Receiver registration (Cleaned up duplicates)
|
|
85
86
|
mainApplication.activity = mainApplication.activity || [];
|
|
86
87
|
if (!mainApplication.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
|
|
87
88
|
mainApplication.activity.push({
|
|
@@ -95,48 +96,34 @@ function withAndroidConfig(config) {
|
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
// 4. Service Setup
|
|
99
99
|
mainApplication.service = mainApplication.service || [];
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
];
|
|
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' } }] }]
|
|
106
|
+
});
|
|
107
|
+
}
|
|
110
108
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
120
|
-
if (svc.intentFilter) {
|
|
121
|
-
serviceEntry['intent-filter'] = [{ action: [{ $: { 'android:name': svc.intentFilter } }] }];
|
|
122
|
-
}
|
|
123
|
-
mainApplication.service.push(serviceEntry);
|
|
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'
|
|
124
117
|
}
|
|
125
|
-
}
|
|
118
|
+
};
|
|
126
119
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (!mainApplication.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
|
|
130
|
-
mainApplication.receiver.push({
|
|
131
|
-
$: { 'android:name': 'com.rnsnativecall.CallActionReceiver', 'android:exported': 'false' }
|
|
132
|
-
});
|
|
133
|
-
}
|
|
120
|
+
if (headlessIndex > -1) mainApplication.service[headlessIndex] = headlessEntry;
|
|
121
|
+
else mainApplication.service.push(headlessEntry);
|
|
134
122
|
|
|
135
123
|
return config;
|
|
136
124
|
});
|
|
137
125
|
}
|
|
138
126
|
|
|
139
|
-
/** 3. IOS CONFIG **/
|
|
140
127
|
function withIosConfig(config) {
|
|
141
128
|
return withInfoPlist(config, (config) => {
|
|
142
129
|
const infoPlist = config.modResults;
|
package/withCallNativeConfig.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
///withCallNativeConfig.js
|
|
2
|
-
const { withAndroidManifest, withInfoPlist, withPlugins, withMainActivity } = require('@expo/config-plugins');
|
|
3
|
-
|
|
4
|
-
/** 1. ANDROID MAIN ACTIVITY MODS **/
|
|
5
|
-
function withMainActivityDataFix(config) {
|
|
6
|
-
return withMainActivity(config, (config) => {
|
|
7
|
-
let contents = config.modResults.contents;
|
|
8
|
-
|
|
9
|
-
// Ensure necessary Imports
|
|
10
|
-
if (!contents.includes('import android.content.Intent')) {
|
|
11
|
-
contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
|
|
12
|
-
}
|
|
13
|
-
if (!contents.includes('import android.os.Bundle')) {
|
|
14
|
-
contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.os.Bundle`);
|
|
15
|
-
}
|
|
16
|
-
|
|
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 by a background task, push to back immediately
|
|
28
|
-
if (intent.getBooleanExtra("background_wake", false)) {
|
|
29
|
-
moveTaskToBack(true)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
// Insertion logic
|
|
35
|
-
if (!contents.includes('override fun onNewIntent')) {
|
|
36
|
-
contents = contents.replace(/class MainActivity : .*/, (match) => `${match}\n${onNewIntentCode}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (!contents.includes('override fun onCreate')) {
|
|
40
|
-
// Find the end of the class or after onNewIntent
|
|
41
|
-
const lastBraceIndex = contents.lastIndexOf('}');
|
|
42
|
-
contents = contents.slice(0, lastBraceIndex) + onCreateCode + contents.slice(lastBraceIndex);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
config.modResults.contents = contents;
|
|
46
|
-
return config;
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 2. ANDROID MANIFEST CONFIG **/
|
|
51
|
-
function withAndroidConfig(config) {
|
|
52
|
-
return withAndroidManifest(config, (config) => {
|
|
53
|
-
const manifest = config.modResults.manifest;
|
|
54
|
-
const application = manifest.application[0];
|
|
55
|
-
|
|
56
|
-
// 1. Permissions (Updated for Android 14 compatibility)
|
|
57
|
-
const permissions = [
|
|
58
|
-
'android.permission.USE_FULL_SCREEN_INTENT',
|
|
59
|
-
'android.permission.VIBRATE',
|
|
60
|
-
'android.permission.FOREGROUND_SERVICE',
|
|
61
|
-
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
|
|
62
|
-
'android.permission.POST_NOTIFICATIONS',
|
|
63
|
-
'android.permission.WAKE_LOCK',
|
|
64
|
-
'android.permission.DISABLE_KEYGUARD',
|
|
65
|
-
'android.permission.RECEIVE_BOOT_COMPLETED'
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
manifest['uses-permission'] = manifest['uses-permission'] || [];
|
|
69
|
-
permissions.forEach((perm) => {
|
|
70
|
-
if (!manifest['uses-permission'].some((p) => p.$['android:name'] === perm)) {
|
|
71
|
-
manifest['uses-permission'].push({ $: { 'android:name': perm } });
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// 2. Activity Setup (AcceptCallActivity)
|
|
76
|
-
application.activity = application.activity || [];
|
|
77
|
-
if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
|
|
78
|
-
application.activity.push({
|
|
79
|
-
$: {
|
|
80
|
-
'android:name': 'com.rnsnativecall.AcceptCallActivity',
|
|
81
|
-
'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
|
|
82
|
-
'android:excludeFromRecents': 'true',
|
|
83
|
-
'android:noHistory': 'true',
|
|
84
|
-
'android:exported': 'false',
|
|
85
|
-
'android:launchMode': 'singleInstance',
|
|
86
|
-
'android:showWhenLocked': 'true',
|
|
87
|
-
'android:turnScreenOn': 'true'
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 3. Service Registration (The Headless Enforcer)
|
|
93
|
-
application.service = application.service || [];
|
|
94
|
-
|
|
95
|
-
// Firebase Messaging Service
|
|
96
|
-
if (!application.service.some(s => s.$['android:name'] === 'com.rnsnativecall.CallMessagingService')) {
|
|
97
|
-
application.service.push({
|
|
98
|
-
$: {
|
|
99
|
-
'android:name': 'com.rnsnativecall.CallMessagingService',
|
|
100
|
-
'android:exported': 'false'
|
|
101
|
-
},
|
|
102
|
-
'intent-filter': [{
|
|
103
|
-
action: [{ $: { 'android:name': 'com.google.firebase.MESSAGING_EVENT' } }]
|
|
104
|
-
}]
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Headless Task Service
|
|
109
|
-
if (!application.service.some(s => s.$['android:name'] === 'com.rnsnativecall.CallHeadlessTask')) {
|
|
110
|
-
application.service.push({
|
|
111
|
-
$: {
|
|
112
|
-
'android:name': 'com.rnsnativecall.CallHeadlessTask',
|
|
113
|
-
'android:exported': 'false' // Headless JS should be internal only
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// 4. Action Receiver
|
|
119
|
-
application.receiver = application.receiver || [];
|
|
120
|
-
if (!application.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
|
|
121
|
-
application.receiver.push({
|
|
122
|
-
$: { 'android:name': 'com.rnsnativecall.CallActionReceiver', 'android:exported': 'false' }
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return config;
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** 3. IOS CONFIG **/
|
|
131
|
-
function withIosConfig(config) {
|
|
132
|
-
return withInfoPlist(config, (config) => {
|
|
133
|
-
const infoPlist = config.modResults;
|
|
134
|
-
if (!infoPlist.UIBackgroundModes) infoPlist.UIBackgroundModes = [];
|
|
135
|
-
['voip', 'audio', 'remote-notification'].forEach(mode => {
|
|
136
|
-
if (!infoPlist.UIBackgroundModes.includes(mode)) infoPlist.UIBackgroundModes.push(mode);
|
|
137
|
-
});
|
|
138
|
-
return config;
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
module.exports = (config) => {
|
|
143
|
-
return withPlugins(config, [
|
|
144
|
-
withAndroidConfig,
|
|
145
|
-
withMainActivityDataFix,
|
|
146
|
-
withIosConfig
|
|
147
|
-
]);
|
|
148
|
-
};
|
package/withCallPermissions.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
//withCallPermissions.js
|
|
2
|
-
const { withPlugins, withAndroidManifest } = require('@expo/config-plugins');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/** 2. ANDROID MANIFEST MOD **/
|
|
6
|
-
function withCallPermissions(config) {
|
|
7
|
-
return withAndroidManifest(config, async (config) => {
|
|
8
|
-
const androidManifest = config.modResults.manifest;
|
|
9
|
-
|
|
10
|
-
// 1. Add the Full Screen Intent Permission
|
|
11
|
-
if (!androidManifest["uses-permission"]) {
|
|
12
|
-
androidManifest["uses-permission"] = [];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const hasPermission = androidManifest["uses-permission"].some(
|
|
16
|
-
(p) => p.$["android:name"] === "android.permission.USE_FULL_SCREEN_INTENT"
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
if (!hasPermission) {
|
|
20
|
-
androidManifest["uses-permission"].push({
|
|
21
|
-
$: { "android:name": "android.permission.USE_FULL_SCREEN_INTENT" },
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 2. Ensure the Activity has the correct flags to appear over lockscreen
|
|
26
|
-
const mainApplication = androidManifest.application[0];
|
|
27
|
-
const mainActivity = mainApplication.activity.find(
|
|
28
|
-
(a) => a.$["android:name"] === ".MainActivity"
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
if (mainActivity) {
|
|
32
|
-
mainActivity.$["android:showOnLockScreen"] = "true";
|
|
33
|
-
mainActivity.$["android:lockTaskMode"] = "if_whitelisted";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return config;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
module.exports = (config) => {
|
|
41
|
-
return withPlugins(config, [withCallPermissions]);
|
|
42
|
-
};
|