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
- // --- THE COLD START FIX ---
29
- // 3. STORE DATA FOR JS POLLING
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
- // 5. LAUNCH OR BRING MAIN APP TO FOREGROUND
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
- // Ensure this is actually a call offer before proceeding
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
- } else if (eventName == "onCallAccepted" && params is Map<*, *>) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.3.1",
3
+ "version": "0.3.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",