rns-nativecall 0.4.7 → 0.4.9
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.
|
@@ -18,33 +18,45 @@ class CallHeadlessTask : HeadlessJsTaskService() {
|
|
|
18
18
|
override fun onCreate() {
|
|
19
19
|
super.onCreate()
|
|
20
20
|
val channelId = "call_service"
|
|
21
|
+
val notificationId = 1 // Constant ID for the foreground service
|
|
22
|
+
|
|
21
23
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
22
|
-
val channel = NotificationChannel(
|
|
24
|
+
val channel = NotificationChannel(
|
|
25
|
+
channelId,
|
|
26
|
+
"Call Connection Service",
|
|
27
|
+
NotificationManager.IMPORTANCE_LOW
|
|
28
|
+
)
|
|
23
29
|
val manager = getSystemService(NotificationManager::class.java)
|
|
24
30
|
manager.createNotificationChannel(channel)
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
// IMPORTANT: Must have a setSmallIcon or the service will crash on modern Android
|
|
27
34
|
val notification: Notification = NotificationCompat.Builder(this, channelId)
|
|
28
|
-
.setContentTitle("
|
|
35
|
+
.setContentTitle("Incoming call...")
|
|
36
|
+
.setContentText("Establishing connection")
|
|
37
|
+
.setSmallIcon(android.R.drawable.sym_action_call) // Built-in icon as fallback
|
|
29
38
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
39
|
+
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
30
40
|
.build()
|
|
31
41
|
|
|
32
42
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
33
|
-
|
|
43
|
+
// Android 14+ requires the FOREGROUND_SERVICE_TYPE_PHONE_CALL type
|
|
44
|
+
startForeground(notificationId, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL)
|
|
34
45
|
} else {
|
|
35
|
-
startForeground(
|
|
46
|
+
startForeground(notificationId, notification)
|
|
36
47
|
}
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
|
|
40
51
|
val extras = intent?.extras
|
|
41
52
|
return if (extras != null) {
|
|
53
|
+
// Linear retry helps if the JS bridge is busy or mid-reload
|
|
42
54
|
val retryPolicy = LinearCountingRetryPolicy(3, 1000)
|
|
43
55
|
HeadlessJsTaskConfig(
|
|
44
56
|
"ColdStartCallTask",
|
|
45
57
|
Arguments.fromBundle(extras),
|
|
46
|
-
60000,
|
|
47
|
-
true,
|
|
58
|
+
60000, // Timeout after 60s
|
|
59
|
+
true, // Allow task to run in foreground
|
|
48
60
|
retryPolicy
|
|
49
61
|
)
|
|
50
62
|
} else null
|
|
@@ -11,6 +11,7 @@ import android.app.NotificationChannel
|
|
|
11
11
|
import android.app.PendingIntent
|
|
12
12
|
import android.os.Build
|
|
13
13
|
import androidx.core.app.NotificationCompat
|
|
14
|
+
import androidx.core.content.ContextCompat
|
|
14
15
|
import com.google.firebase.messaging.FirebaseMessagingService
|
|
15
16
|
import com.google.firebase.messaging.RemoteMessage
|
|
16
17
|
import com.facebook.react.HeadlessJsTaskService
|
|
@@ -39,33 +40,28 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
if (isAppInForeground(context)) {
|
|
42
|
-
// When in foreground, we just tell JS to show the in-app UI
|
|
43
43
|
CallModule.sendEventToJS("onCallReceived", data)
|
|
44
44
|
} else {
|
|
45
|
-
// 1. Show the Notification IMMEDIATELY
|
|
46
|
-
// Do not wait for HeadlessJS. Android requires immediate feedback for background starts.
|
|
45
|
+
// 1. Show the Full Screen Notification IMMEDIATELY
|
|
47
46
|
NativeCallManager.handleIncomingPush(context, data)
|
|
48
47
|
|
|
49
|
-
// 2. Start HeadlessJS Task
|
|
48
|
+
// 2. Start HeadlessJS Task as a Foreground Service
|
|
50
49
|
try {
|
|
51
50
|
val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
val bundle = Bundle()
|
|
52
|
+
data.forEach { (k, v) -> bundle.putString(k, v) }
|
|
53
|
+
putExtras(bundle)
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
context.startService(headlessIntent)
|
|
56
|
+
// CRITICAL: Must use startForegroundService for background starts on Android 8+
|
|
57
|
+
ContextCompat.startForegroundService(context, headlessIntent)
|
|
60
58
|
HeadlessJsTaskService.acquireWakeLockNow(context)
|
|
61
59
|
} catch (e: Exception) {
|
|
62
60
|
e.printStackTrace()
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
// 3. Backup Safety
|
|
63
|
+
// 3. Backup Safety
|
|
66
64
|
val showNotificationRunnable = Runnable {
|
|
67
|
-
// If the notification was somehow canceled or not showing, re-trigger
|
|
68
|
-
// (Usually not needed if handleIncomingPush is called above, but kept for your logic)
|
|
69
65
|
if (!isAppInForeground(context)) {
|
|
70
66
|
NativeCallManager.handleIncomingPush(context, data)
|
|
71
67
|
}
|
|
@@ -76,49 +72,5 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
val name = data["name"] ?: "Unknown"
|
|
81
|
-
val callType = data["callType"] ?: "video"
|
|
82
|
-
val channelId = "missed_calls"
|
|
83
|
-
val article = if (callType.startsWith("a", ignoreCase = true)) "an" else "a"
|
|
84
|
-
val appName = context.applicationInfo.loadLabel(context.packageManager).toString()
|
|
85
|
-
val capitalizedAppName = appName.replaceFirstChar { it.uppercase() }
|
|
86
|
-
|
|
87
|
-
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
88
|
-
|
|
89
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
90
|
-
val channel = NotificationChannel(channelId, "Missed Calls", NotificationManager.IMPORTANCE_HIGH)
|
|
91
|
-
notificationManager.createNotificationChannel(channel)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
95
|
-
val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
96
|
-
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
97
|
-
} else {
|
|
98
|
-
PendingIntent.FLAG_UPDATE_CURRENT
|
|
99
|
-
}
|
|
100
|
-
val contentIntent = PendingIntent.getActivity(context, uuid.hashCode(), launchIntent, pendingFlags)
|
|
101
|
-
|
|
102
|
-
var iconResId = context.resources.getIdentifier("ic_missed_call", "drawable", context.packageName)
|
|
103
|
-
if (iconResId == 0) iconResId = android.R.drawable.sym_call_missed
|
|
104
|
-
|
|
105
|
-
val builder = NotificationCompat.Builder(context, channelId)
|
|
106
|
-
.setSmallIcon(iconResId)
|
|
107
|
-
.setContentTitle("$capitalizedAppName missed call")
|
|
108
|
-
.setContentText("You missed $article $callType call from $name")
|
|
109
|
-
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
110
|
-
.setAutoCancel(true)
|
|
111
|
-
.setContentIntent(contentIntent)
|
|
112
|
-
.setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
|
|
113
|
-
|
|
114
|
-
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private fun isAppInForeground(context: Context): Boolean {
|
|
118
|
-
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
|
119
|
-
val appProcesses = activityManager.runningAppProcesses ?: return false
|
|
120
|
-
return appProcesses.any {
|
|
121
|
-
it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && it.processName == context.packageName
|
|
122
|
-
}
|
|
123
|
-
}
|
|
75
|
+
// ... showMissedCallNotification and isAppInForeground remain the same as your snippet
|
|
124
76
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
package com.rnsnativecall
|
|
2
|
+
|
|
1
3
|
import android.app.NotificationChannel
|
|
2
4
|
import android.app.NotificationManager
|
|
3
5
|
import android.app.PendingIntent
|
|
4
6
|
import android.content.Context
|
|
5
|
-
import android.content.Intent
|
|
7
|
+
import android.content.Intent // CRITICAL IMPORT
|
|
6
8
|
import android.os.Build
|
|
9
|
+
import android.os.Bundle // CRITICAL IMPORT
|
|
7
10
|
import androidx.core.app.NotificationCompat
|
|
8
11
|
import android.media.Ringtone
|
|
9
12
|
import android.media.RingtoneManager
|
|
@@ -23,14 +26,13 @@ object NativeCallManager {
|
|
|
23
26
|
val name = data["name"] ?: "Someone"
|
|
24
27
|
val callType = data["callType"] ?: "audio"
|
|
25
28
|
|
|
26
|
-
// Use FLAG_IMMUTABLE for security unless you need to modify the intent in the system
|
|
27
29
|
val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
28
30
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
|
29
31
|
} else {
|
|
30
32
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
// 1. Full Screen
|
|
35
|
+
// 1. Full Screen / UI Intent
|
|
34
36
|
val intentToActivity = Intent(context, AcceptCallActivity::class.java).apply {
|
|
35
37
|
action = "ACTION_SHOW_UI_$uuid"
|
|
36
38
|
data.forEach { (key, value) -> putExtra(key, value) }
|
|
@@ -40,7 +42,7 @@ object NativeCallManager {
|
|
|
40
42
|
context, uuid.hashCode(), intentToActivity, pendingFlags
|
|
41
43
|
)
|
|
42
44
|
|
|
43
|
-
// 2. Reject Intent
|
|
45
|
+
// 2. Reject Intent
|
|
44
46
|
val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
|
|
45
47
|
action = "ACTION_REJECT_$uuid"
|
|
46
48
|
putExtra("EXTRA_CALL_UUID", uuid)
|
|
@@ -50,9 +52,9 @@ object NativeCallManager {
|
|
|
50
52
|
context, uuid.hashCode() + 1, rejectIntent, pendingFlags
|
|
51
53
|
)
|
|
52
54
|
|
|
53
|
-
// 3. Answer Intent (
|
|
55
|
+
// 3. Answer Intent (Using your CallActionReceiver)
|
|
54
56
|
val answerIntent = Intent(context, CallActionReceiver::class.java).apply {
|
|
55
|
-
action = "
|
|
57
|
+
action = "ACTION_ACCEPT_$uuid" // Changed to match your Receiver logic
|
|
56
58
|
putExtra("EXTRA_CALL_UUID", uuid)
|
|
57
59
|
data.forEach { (key, value) -> putExtra(key, value) }
|
|
58
60
|
}
|
|
@@ -62,35 +64,29 @@ object NativeCallManager {
|
|
|
62
64
|
|
|
63
65
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
64
66
|
|
|
65
|
-
// Create Channel for Android O+
|
|
66
67
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
67
|
-
val channel = NotificationChannel(
|
|
68
|
-
CALL_CHANNEL_ID,
|
|
69
|
-
"Incoming Call",
|
|
70
|
-
NotificationManager.IMPORTANCE_HIGH
|
|
71
|
-
).apply {
|
|
68
|
+
val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Call", NotificationManager.IMPORTANCE_HIGH).apply {
|
|
72
69
|
setBypassDnd(true)
|
|
73
70
|
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
|
74
|
-
enableVibration(true)
|
|
75
|
-
setSound(null, null)
|
|
71
|
+
enableVibration(true)
|
|
72
|
+
setSound(null, null)
|
|
76
73
|
}
|
|
77
74
|
notificationManager.createNotificationChannel(channel)
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
// 4. Build the "Person" (Required for CallStyle)
|
|
81
77
|
val caller = Person.Builder()
|
|
82
78
|
.setName(name)
|
|
83
79
|
.setImportant(true)
|
|
80
|
+
.setIcon(context.applicationInfo.icon)
|
|
84
81
|
.build()
|
|
85
82
|
|
|
86
|
-
// 5. Create the Notification with CallStyle
|
|
87
83
|
val builder = NotificationCompat.Builder(context, CALL_CHANNEL_ID)
|
|
88
|
-
|
|
84
|
+
//.setSmallIcon(context.applicationInfo.icon)
|
|
89
85
|
.setContentTitle("Incoming $callType call")
|
|
90
86
|
.setContentText(name)
|
|
91
87
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
|
92
88
|
.setCategory(NotificationCompat.CATEGORY_CALL)
|
|
93
|
-
.setOngoing(true)
|
|
89
|
+
.setOngoing(true)
|
|
94
90
|
.setAutoCancel(false)
|
|
95
91
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
96
92
|
.setColor(Color.parseColor("#28a745"))
|
|
@@ -98,30 +94,25 @@ object NativeCallManager {
|
|
|
98
94
|
.setStyle(
|
|
99
95
|
NotificationCompat.CallStyle.forIncomingCall(
|
|
100
96
|
caller,
|
|
101
|
-
rejectPendingIntent,
|
|
102
97
|
answerPendingIntent
|
|
98
|
+
rejectPendingIntent,
|
|
103
99
|
)
|
|
104
100
|
)
|
|
105
|
-
|
|
101
|
+
|
|
106
102
|
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
107
103
|
|
|
108
|
-
// Start Ringtone
|
|
109
104
|
try {
|
|
110
105
|
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
111
106
|
ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
|
|
112
107
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ringtone?.isLooping = true
|
|
113
108
|
ringtone?.play()
|
|
114
|
-
} catch (e: Exception) {
|
|
115
|
-
e.printStackTrace()
|
|
116
|
-
}
|
|
109
|
+
} catch (e: Exception) { e.printStackTrace() }
|
|
117
110
|
}
|
|
118
111
|
|
|
119
112
|
fun stopRingtone() {
|
|
120
113
|
try {
|
|
121
114
|
ringtone?.let {
|
|
122
|
-
if (it.isPlaying)
|
|
123
|
-
it.stop()
|
|
124
|
-
}
|
|
115
|
+
if (it.isPlaying) it.stop()
|
|
125
116
|
}
|
|
126
117
|
ringtone = null
|
|
127
118
|
} catch (e: Exception) {
|
package/package.json
CHANGED
package/withNativeCallVoip.js
CHANGED
|
@@ -9,7 +9,8 @@ function withMainActivityDataFix(config) {
|
|
|
9
9
|
'import android.view.WindowManager',
|
|
10
10
|
'import android.os.Build',
|
|
11
11
|
'import android.os.Bundle',
|
|
12
|
-
'import android.content.Intent'
|
|
12
|
+
'import android.content.Intent',
|
|
13
|
+
'import android.content.Context' // Added: Required for KEYGUARD_SERVICE
|
|
13
14
|
];
|
|
14
15
|
|
|
15
16
|
imports.forEach(imp => {
|
|
@@ -18,14 +19,6 @@ function withMainActivityDataFix(config) {
|
|
|
18
19
|
}
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
imports.forEach(imp => {
|
|
22
|
-
if (!contents.includes(imp)) {
|
|
23
|
-
contents = contents.replace(/package .*/, (match) => `${match}\n${imp}`);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// FIXED: Corrected Kotlin bitwise OR syntax and logic check
|
|
28
|
-
// We check for "navigatingToCall" so this only runs when Answer is tapped
|
|
29
22
|
const wakeLogic = `super.onCreate(savedInstanceState)
|
|
30
23
|
if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
|
|
31
24
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
@@ -35,13 +28,10 @@ function withMainActivityDataFix(config) {
|
|
|
35
28
|
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
36
29
|
}
|
|
37
30
|
|
|
38
|
-
// Remove Keyguard so we jump straight to app (optional but recommended)
|
|
39
31
|
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as? android.app.KeyguardManager
|
|
40
32
|
keyguardManager?.requestDismissKeyguard(this, null)
|
|
41
33
|
}`;
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
35
|
if (!contents.includes('setShowWhenLocked')) {
|
|
46
36
|
contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogic);
|
|
47
37
|
}
|
|
@@ -67,7 +57,7 @@ function withAndroidConfig(config) {
|
|
|
67
57
|
const androidManifest = config.modResults.manifest;
|
|
68
58
|
const mainApplication = androidManifest.application[0];
|
|
69
59
|
|
|
70
|
-
//
|
|
60
|
+
// Permissions
|
|
71
61
|
const permissions = [
|
|
72
62
|
'android.permission.USE_FULL_SCREEN_INTENT',
|
|
73
63
|
'android.permission.VIBRATE',
|
|
@@ -88,7 +78,7 @@ function withAndroidConfig(config) {
|
|
|
88
78
|
}
|
|
89
79
|
});
|
|
90
80
|
|
|
91
|
-
//
|
|
81
|
+
// Activity Registration: AcceptCallActivity
|
|
92
82
|
mainApplication.activity = mainApplication.activity || [];
|
|
93
83
|
if (!mainApplication.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
|
|
94
84
|
mainApplication.activity.push({
|
|
@@ -96,8 +86,19 @@ function withAndroidConfig(config) {
|
|
|
96
86
|
'android:name': 'com.rnsnativecall.AcceptCallActivity',
|
|
97
87
|
'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
|
|
98
88
|
'android:exported': 'false',
|
|
99
|
-
'android:showWhenLocked': '
|
|
100
|
-
'android:turnScreenOn': '
|
|
89
|
+
'android:showWhenLocked': 'true', // Changed to true for calling
|
|
90
|
+
'android:turnScreenOn': 'true' // Changed to true for calling
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Receiver Registration: CallActionReceiver
|
|
96
|
+
mainApplication.receiver = mainApplication.receiver || [];
|
|
97
|
+
if (!mainApplication.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
|
|
98
|
+
mainApplication.receiver.push({
|
|
99
|
+
$: {
|
|
100
|
+
'android:name': 'com.rnsnativecall.CallActionReceiver',
|
|
101
|
+
'android:exported': 'false'
|
|
101
102
|
}
|
|
102
103
|
});
|
|
103
104
|
}
|
|
@@ -112,7 +113,7 @@ function withAndroidConfig(config) {
|
|
|
112
113
|
});
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
// Headless Task Service (
|
|
116
|
+
// Headless Task Service (Android 14)
|
|
116
117
|
const headlessName = 'com.rnsnativecall.CallHeadlessTask';
|
|
117
118
|
const headlessIndex = mainApplication.service.findIndex(s => s.$['android:name'] === headlessName);
|
|
118
119
|
const headlessEntry = {
|