rns-nativecall 0.3.1 → 0.3.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.
|
@@ -10,12 +10,13 @@ class AcceptCallActivity : Activity() {
|
|
|
10
10
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
11
11
|
super.onCreate(savedInstanceState)
|
|
12
12
|
NativeCallManager.stopRingtone()
|
|
13
|
-
|
|
14
13
|
// 1. CLEAR THE NOTIFICATION
|
|
14
|
+
// Use the same ID (101) used in NativeCallManager
|
|
15
15
|
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
16
16
|
notificationManager.cancel(101)
|
|
17
17
|
|
|
18
18
|
// 2. EXTRACT DATA SAFELY
|
|
19
|
+
// We iterate through all extras and convert them to Strings for the JS Map
|
|
19
20
|
val dataMap = mutableMapOf<String, String>()
|
|
20
21
|
val extras = intent.extras
|
|
21
22
|
extras?.keySet()?.forEach { key ->
|
|
@@ -25,26 +26,30 @@ class AcceptCallActivity : Activity() {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// We save this in a static variable inside CallModule so getInitialCallData() can find it
|
|
31
|
-
CallModule.setPendingCallData(dataMap)
|
|
32
|
-
|
|
33
|
-
// 4. EMIT EVENT TO REACT NATIVE
|
|
34
|
-
// This works if the app is already running
|
|
29
|
+
// 3. EMIT EVENT TO REACT NATIVE
|
|
30
|
+
// This handles the case where the app is already in the background/foreground
|
|
35
31
|
CallModule.sendEventToJS("onCallAccepted", dataMap)
|
|
36
32
|
|
|
37
|
-
//
|
|
33
|
+
// 4. LAUNCH OR BRING MAIN APP TO FOREGROUND
|
|
38
34
|
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
39
35
|
if (launchIntent != null) {
|
|
40
36
|
launchIntent.apply {
|
|
37
|
+
// FLAG_ACTIVITY_SINGLE_TOP: Updates the app if it's already open
|
|
38
|
+
// FLAG_ACTIVITY_NEW_TASK: Required when starting from a non-activity context
|
|
41
39
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
40
|
+
|
|
41
|
+
// Copy all call data to the launch intent
|
|
42
42
|
putExtras(extras ?: Bundle())
|
|
43
|
+
|
|
44
|
+
// Helper flag for your React Native logic
|
|
43
45
|
putExtra("navigatingToCall", true)
|
|
44
46
|
}
|
|
45
47
|
startActivity(launchIntent)
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
|
|
50
|
+
CallModule.setPendingCallData(dataMap)
|
|
51
|
+
// 5. FINISH TRAMPOLINE
|
|
52
|
+
// This ensures this invisible activity doesn't stay in the "Recent Apps" list
|
|
48
53
|
finish()
|
|
49
54
|
}
|
|
50
55
|
}
|
|
@@ -2,6 +2,9 @@ package com.rnsnativecall
|
|
|
2
2
|
|
|
3
3
|
import android.app.ActivityManager
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.os.Handler
|
|
7
|
+
import android.os.Looper
|
|
5
8
|
import com.google.firebase.messaging.FirebaseMessagingService
|
|
6
9
|
import com.google.firebase.messaging.RemoteMessage
|
|
7
10
|
|
|
@@ -9,18 +12,59 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
9
12
|
|
|
10
13
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
|
11
14
|
val data = remoteMessage.data
|
|
15
|
+
val context = applicationContext
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
if (data["type"] == "offer" || data.containsKey("callUuid")) {
|
|
15
|
-
|
|
16
|
-
// ALWAYS trigger the native logic.
|
|
17
|
-
// The NativeCallManager should decide if it needs to show a Notification
|
|
18
|
-
// or just play a sound. This ensures the "Ringtone" starts natively
|
|
19
|
-
// which is more reliable than JS audio.
|
|
20
|
-
NativeCallManager.handleIncomingPush(applicationContext, data)
|
|
21
|
-
|
|
22
|
-
// Still notify JS so it can update the UI or pre-load the call screen
|
|
17
|
+
if (isAppInForeground(context)) {
|
|
23
18
|
CallModule.sendEventToJS("onCallReceived", data)
|
|
19
|
+
} else {
|
|
20
|
+
// 1. START WAKING THE APP IMMEDIATELY
|
|
21
|
+
// We launch the "AcceptCallActivity" with a special flag
|
|
22
|
+
// OR just trigger the Bridge via an Intent.
|
|
23
|
+
wakeUpReactContext(context, data)
|
|
24
|
+
|
|
25
|
+
// 2. Start the 18-second "Safety" timer
|
|
26
|
+
val handler = Handler(Looper.getMainLooper())
|
|
27
|
+
handler.postDelayed({
|
|
28
|
+
// Re-check: If the bridge woke up and the user navigated in-app,
|
|
29
|
+
// isAppInForeground will now be true.
|
|
30
|
+
if (!isAppInForeground(context)) {
|
|
31
|
+
NativeCallManager.handleIncomingPush(context, data)
|
|
32
|
+
}
|
|
33
|
+
}, 18000)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private fun wakeUpReactContext(context: Context, data: Map<String, String>) {
|
|
38
|
+
try {
|
|
39
|
+
// We use the Launch Intent to trigger the splash/main activity boot sequence
|
|
40
|
+
// but we don't bring it to the front yet (it stays in background process)
|
|
41
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
42
|
+
launchIntent?.apply {
|
|
43
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
44
|
+
// Add a hidden flag so your Splash screen knows it's a "silent boot"
|
|
45
|
+
putExtra("silent_wake", true)
|
|
46
|
+
data.forEach { (key, value) -> putExtra(key, value) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// This starts the process and the React Native Bridge
|
|
50
|
+
// Note: On Android 10+, this won't show the UI, but it WILL start the process.
|
|
51
|
+
context.startActivity(launchIntent)
|
|
52
|
+
} catch (e: Exception) {
|
|
53
|
+
// Log error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private fun isAppInForeground(context: Context): Boolean {
|
|
58
|
+
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
|
59
|
+
val appProcesses = activityManager.runningAppProcesses ?: return false
|
|
60
|
+
val packageName = context.packageName
|
|
61
|
+
|
|
62
|
+
for (appProcess in appProcesses) {
|
|
63
|
+
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
|
|
64
|
+
appProcess.processName == packageName) {
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
24
67
|
}
|
|
68
|
+
return false
|
|
25
69
|
}
|
|
26
70
|
}
|
|
@@ -23,7 +23,7 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
23
23
|
writableMap
|
|
24
24
|
}
|
|
25
25
|
promise.resolve(data)
|
|
26
|
-
pendingCallDataMap = null
|
|
26
|
+
pendingCallDataMap = null // Clear after use
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
@ReactMethod
|
|
@@ -34,6 +34,7 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
34
34
|
"name" to name,
|
|
35
35
|
"callType" to callType
|
|
36
36
|
)
|
|
37
|
+
// Use reactApplicationContext safely here
|
|
37
38
|
NativeCallManager.handleIncomingPush(reactApplicationContext, data)
|
|
38
39
|
promise.resolve(true)
|
|
39
40
|
} catch (e: Exception) {
|
|
@@ -43,17 +44,13 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
43
44
|
|
|
44
45
|
@ReactMethod
|
|
45
46
|
fun endNativeCall(uuid: String) {
|
|
46
|
-
// Since we aren't using Telecom, we just stop the ringtone and clear notification
|
|
47
47
|
NativeCallManager.stopRingtone()
|
|
48
48
|
pendingCallDataMap = null
|
|
49
49
|
|
|
50
50
|
val notificationManager = reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
51
51
|
notificationManager.cancel(101)
|
|
52
|
-
|
|
53
|
-
// No more ConnectionService to destroy!
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
// This is a dummy now since we don't need PhoneAccount permissions anymore
|
|
57
54
|
@ReactMethod
|
|
58
55
|
fun checkTelecomPermissions(promise: Promise) {
|
|
59
56
|
promise.resolve(true)
|
|
@@ -66,6 +63,7 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
66
63
|
private var instance: CallModule? = null
|
|
67
64
|
private var pendingCallDataMap: Map<String, String>? = null
|
|
68
65
|
|
|
66
|
+
// This is the static gate accessible from AcceptCallActivity
|
|
69
67
|
@JvmStatic
|
|
70
68
|
fun setPendingCallData(data: Map<String, String>) {
|
|
71
69
|
pendingCallDataMap = data
|
|
@@ -86,11 +84,15 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
86
84
|
else -> null
|
|
87
85
|
}
|
|
88
86
|
|
|
87
|
+
// If app is alive, send via Bridge
|
|
89
88
|
if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
|
|
90
89
|
reactContext
|
|
91
90
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
92
91
|
?.emit(eventName, bridgeData)
|
|
93
|
-
}
|
|
92
|
+
}
|
|
93
|
+
// If app is in killed state, save for polling
|
|
94
|
+
else if (eventName == "onCallAccepted" && params is Map<*, *>) {
|
|
95
|
+
@Suppress("UNCHECKED_CAST")
|
|
94
96
|
setPendingCallData(params as Map<String, String>)
|
|
95
97
|
}
|
|
96
98
|
}
|
package/package.json
CHANGED