rns-nativecall 0.3.4 → 0.3.5
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.
|
@@ -9,11 +9,12 @@ class CallHeadlessTask : HeadlessJsTaskService() {
|
|
|
9
9
|
override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? {
|
|
10
10
|
val extras = intent.extras
|
|
11
11
|
return if (extras != null) {
|
|
12
|
+
// INCREASE timeout to 30000 (30s) so it covers your 18s delay
|
|
12
13
|
HeadlessJsTaskConfig(
|
|
13
|
-
"ColdStartCallTask",
|
|
14
|
+
"ColdStartCallTask",
|
|
14
15
|
Arguments.fromBundle(extras),
|
|
15
|
-
|
|
16
|
-
true
|
|
16
|
+
30000,
|
|
17
|
+
true // Allow in foreground
|
|
17
18
|
)
|
|
18
19
|
} else {
|
|
19
20
|
null
|
|
@@ -6,58 +6,108 @@ import android.content.Intent
|
|
|
6
6
|
import android.os.Bundle
|
|
7
7
|
import android.os.Handler
|
|
8
8
|
import android.os.Looper
|
|
9
|
+
import android.app.NotificationManager
|
|
10
|
+
import androidx.core.app.NotificationCompat
|
|
9
11
|
import com.google.firebase.messaging.FirebaseMessagingService
|
|
10
12
|
import com.google.firebase.messaging.RemoteMessage
|
|
11
13
|
import com.facebook.react.HeadlessJsTaskService
|
|
12
14
|
|
|
13
15
|
class CallMessagingService : FirebaseMessagingService() {
|
|
14
16
|
|
|
17
|
+
// This companion object allows the Cancel logic to find the active timer
|
|
18
|
+
companion object {
|
|
19
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
20
|
+
private val pendingNotifications = mutableMapOf<String, Runnable>()
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
|
16
24
|
val data = remoteMessage.data
|
|
17
25
|
val context = applicationContext
|
|
18
26
|
|
|
27
|
+
// Extract variables for logic
|
|
28
|
+
val uuid = data["callUuid"] ?: return
|
|
29
|
+
val type = data["type"] ?: ""
|
|
30
|
+
|
|
31
|
+
// CASE 1: CALLER HUNG UP / CANCELLED
|
|
32
|
+
if (type == "CANCEL") {
|
|
33
|
+
// 1. Remove the pending 18s timer for this specific call
|
|
34
|
+
pendingNotifications[uuid]?.let {
|
|
35
|
+
handler.removeCallbacks(it)
|
|
36
|
+
pendingNotifications.remove(uuid)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. Stop the ringtone/Pill if it had already started showing
|
|
40
|
+
NativeCallManager.stopRingtone()
|
|
41
|
+
CallHandler.destroyNativeCallUI(uuid) // Ensure native UI is killed
|
|
42
|
+
|
|
43
|
+
// 3. Show standard "Missed Call" notification
|
|
44
|
+
showMissedCallNotification(context, data, uuid)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// CASE 2: NEW INCOMING CALL
|
|
19
49
|
if (isAppInForeground(context)) {
|
|
20
|
-
// App is visible: Send event to active JS bridge
|
|
50
|
+
// App is visible: Send event to active JS bridge immediately
|
|
21
51
|
CallModule.sendEventToJS("onCallReceived", data)
|
|
22
52
|
} else {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
// 1. Wake the actual App process by launching the Intent
|
|
54
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
55
|
+
launchIntent?.apply {
|
|
56
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
57
|
+
// This is key: it tells the app to boot the JS but stay minimized
|
|
58
|
+
putExtra("background_wake", true)
|
|
59
|
+
}
|
|
60
|
+
context.startActivity(launchIntent)
|
|
61
|
+
|
|
62
|
+
// You SHOULD see "Android Bundled..." now because the Activity is starting
|
|
63
|
+
|
|
64
|
+
val showNotificationRunnable = Runnable {
|
|
65
|
+
if (!isAppInForeground(context)) {
|
|
66
|
+
NativeCallManager.handleIncomingPush(context, data)
|
|
36
67
|
}
|
|
37
68
|
}
|
|
69
|
+
handler.postDelayed(showNotificationRunnable, 18000)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
38
72
|
|
|
39
73
|
private fun startHeadlessTask(context: Context, data: Map<String, String>) {
|
|
40
74
|
val serviceIntent = Intent(context, CallHeadlessTask::class.java)
|
|
41
75
|
val bundle = Bundle()
|
|
42
|
-
|
|
43
|
-
// Pass all FCM data to the JS task
|
|
44
76
|
data.forEach { (key, value) -> bundle.putString(key, value) }
|
|
45
77
|
serviceIntent.putExtras(bundle)
|
|
46
78
|
|
|
47
|
-
// Start the service. This triggers the 'ColdStartCallTask' in index.js
|
|
48
79
|
context.startService(serviceIntent)
|
|
49
80
|
HeadlessJsTaskService.acquireWakeLockNow(context)
|
|
50
81
|
}
|
|
51
82
|
|
|
83
|
+
private fun showMissedCallNotification(context: Context, data: Map<String, String>, uuid: String) {
|
|
84
|
+
val name = data["name"] ?: "Unknown"
|
|
85
|
+
val callType = data["callType"] ?: "video"
|
|
86
|
+
val channelId = "missed_calls" // Ensure this channel is created in your MainApplication or CallManager
|
|
87
|
+
|
|
88
|
+
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
89
|
+
|
|
90
|
+
// Use ic_launcher as fallback if ic_missed_call isn't found
|
|
91
|
+
var iconResId = context.resources.getIdentifier("ic_missed_call", "drawable", context.packageName)
|
|
92
|
+
if (iconResId == 0) iconResId = android.R.drawable.sym_call_missed
|
|
93
|
+
|
|
94
|
+
val builder = NotificationCompat.Builder(context, channelId)
|
|
95
|
+
.setSmallIcon(iconResId)
|
|
96
|
+
.setContentTitle("Missed $callType call")
|
|
97
|
+
.setContentText("You missed a call from $name")
|
|
98
|
+
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
99
|
+
.setAutoCancel(true)
|
|
100
|
+
.setCategory(NotificationCompat.CATEGORY_MISS)
|
|
101
|
+
|
|
102
|
+
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
103
|
+
}
|
|
104
|
+
|
|
52
105
|
private fun isAppInForeground(context: Context): Boolean {
|
|
53
106
|
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
|
54
107
|
val appProcesses = activityManager.runningAppProcesses ?: return false
|
|
55
108
|
val packageName = context.packageName
|
|
56
109
|
|
|
57
110
|
for (appProcess in appProcesses) {
|
|
58
|
-
// IMPORTANCE_FOREGROUND only triggers if a real Activity is on screen.
|
|
59
|
-
// Running a Headless Task counts as IMPORTANCE_VISIBLE or lower,
|
|
60
|
-
// so this check remains accurate.
|
|
61
111
|
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
|
|
62
112
|
appProcess.processName == packageName) {
|
|
63
113
|
return true
|
|
@@ -28,7 +28,6 @@ object NativeCallManager {
|
|
|
28
28
|
} else {
|
|
29
29
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
30
30
|
}
|
|
31
|
-
|
|
32
31
|
// 1. IMPROVED INTENT: Direct to AcceptCallActivity
|
|
33
32
|
// This is better than a dummy intent because it allows the OS to
|
|
34
33
|
// launch your "Accept" logic immediately if the phone is locked.
|
package/package.json
CHANGED
package/withNativeCallVoip.js
CHANGED
|
@@ -5,10 +5,15 @@ function withMainActivityDataFix(config) {
|
|
|
5
5
|
return withMainActivity(config, (config) => {
|
|
6
6
|
let contents = config.modResults.contents;
|
|
7
7
|
|
|
8
|
+
// 1. Ensure necessary Imports are present
|
|
8
9
|
if (!contents.includes('import android.content.Intent')) {
|
|
9
10
|
contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
|
|
10
11
|
}
|
|
12
|
+
if (!contents.includes('import android.os.Bundle')) {
|
|
13
|
+
contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.os.Bundle`);
|
|
14
|
+
}
|
|
11
15
|
|
|
16
|
+
// 2. Define the code blocks
|
|
12
17
|
const onNewIntentCode = `
|
|
13
18
|
override fun onNewIntent(intent: Intent) {
|
|
14
19
|
super.onNewIntent(intent)
|
|
@@ -16,11 +21,28 @@ function withMainActivityDataFix(config) {
|
|
|
16
21
|
}
|
|
17
22
|
`;
|
|
18
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
|
|
19
35
|
if (!contents.includes('override fun onNewIntent')) {
|
|
20
36
|
const lastBraceIndex = contents.lastIndexOf('}');
|
|
21
37
|
contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
|
|
22
38
|
}
|
|
23
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
|
+
|
|
24
46
|
config.modResults.contents = contents;
|
|
25
47
|
return config;
|
|
26
48
|
});
|
|
@@ -104,6 +126,8 @@ function withAndroidConfig(config) {
|
|
|
104
126
|
});
|
|
105
127
|
}
|
|
106
128
|
|
|
129
|
+
|
|
130
|
+
|
|
107
131
|
return config;
|
|
108
132
|
});
|
|
109
133
|
}
|