rns-nativecall 0.3.0 → 0.3.2

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,8 @@ package com.rnsnativecall
2
2
 
3
3
  import android.app.ActivityManager
4
4
  import android.content.Context
5
+ import android.os.Handler
6
+ import android.os.Looper
5
7
  import com.google.firebase.messaging.FirebaseMessagingService
6
8
  import com.google.firebase.messaging.RemoteMessage
7
9
 
@@ -10,17 +12,33 @@ class CallMessagingService : FirebaseMessagingService() {
10
12
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
11
13
  val data = remoteMessage.data
12
14
 
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
15
+ if (isAppInForeground(applicationContext)) {
16
+ // 1. App is OPEN: Send to JS immediately (No delay)
23
17
  CallModule.sendEventToJS("onCallReceived", data)
18
+ } else {
19
+ // 2. App is CLOSED/BACKGROUND: Delay the system pill by 18 seconds
20
+ val handler = Handler(Looper.getMainLooper())
21
+ handler.postDelayed({
22
+ // Re-check: If the user opened the app during these 18 seconds,
23
+ // we might want to cancel the pill.
24
+ if (!isAppInForeground(applicationContext)) {
25
+ NativeCallManager.handleIncomingPush(applicationContext, data)
26
+ }
27
+ }, 18000)
24
28
  }
25
29
  }
30
+
31
+ private fun isAppInForeground(context: Context): Boolean {
32
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
33
+ val appProcesses = activityManager.runningAppProcesses ?: return false
34
+ val packageName = context.packageName
35
+
36
+ for (appProcess in appProcesses) {
37
+ if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
38
+ appProcess.processName == packageName) {
39
+ return true
40
+ }
41
+ }
42
+ return false
43
+ }
26
44
  }
@@ -1,14 +1,7 @@
1
1
  package com.rnsnativecall
2
2
 
3
3
  import android.app.NotificationManager
4
- import android.content.ComponentName
5
4
  import android.content.Context
6
- import android.content.Intent
7
- import android.telecom.DisconnectCause
8
- import android.telecom.PhoneAccount
9
- import android.telecom.PhoneAccountHandle
10
- import android.telecom.TelecomManager
11
- import android.widget.Toast
12
5
  import com.facebook.react.bridge.*
13
6
  import com.facebook.react.modules.core.DeviceEventManagerModule
14
7
 
@@ -16,16 +9,12 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
16
9
 
17
10
  init {
18
11
  instance = this
19
- registerPhoneAccount()
20
12
  }
21
13
 
22
14
  override fun getName() = "CallModule"
23
15
 
24
- // ... getPhoneAccountHandle and registerPhoneAccount remain the same ...
25
-
26
16
  @ReactMethod
27
17
  fun getInitialCallData(promise: Promise) {
28
- // Convert our stored simple Map into a WritableMap for JS
29
18
  val data = pendingCallDataMap?.let { map ->
30
19
  val writableMap = Arguments.createMap()
31
20
  map.forEach { (key, value) ->
@@ -33,9 +22,8 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
33
22
  }
34
23
  writableMap
35
24
  }
36
-
37
25
  promise.resolve(data)
38
- pendingCallDataMap = null // Clear after consumption
26
+ pendingCallDataMap = null // Clear after use
39
27
  }
40
28
 
41
29
  @ReactMethod
@@ -46,6 +34,7 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
46
34
  "name" to name,
47
35
  "callType" to callType
48
36
  )
37
+ // Use reactApplicationContext safely here
49
38
  NativeCallManager.handleIncomingPush(reactApplicationContext, data)
50
39
  promise.resolve(true)
51
40
  } catch (e: Exception) {
@@ -56,26 +45,25 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
56
45
  @ReactMethod
57
46
  fun endNativeCall(uuid: String) {
58
47
  NativeCallManager.stopRingtone()
59
- pendingCallDataMap = null // Clear any pending data
48
+ pendingCallDataMap = null
60
49
 
61
50
  val notificationManager = reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
62
51
  notificationManager.cancel(101)
52
+ }
63
53
 
64
- val connection = MyConnectionService.getConnection(uuid)
65
- connection?.let {
66
- it.setDisconnected(DisconnectCause(DisconnectCause.MISSED))
67
- it.destroy()
68
- MyConnectionService.removeConnection(uuid)
69
- }
54
+ @ReactMethod
55
+ fun checkTelecomPermissions(promise: Promise) {
56
+ promise.resolve(true)
70
57
  }
71
58
 
72
- // ... checkTelecomPermissions remains the same ...
59
+ @ReactMethod fun addListener(eventName: String) {}
60
+ @ReactMethod fun removeListeners(count: Int) {}
73
61
 
74
62
  companion object {
75
63
  private var instance: CallModule? = null
76
- // Use a standard Map to store data so we don't depend on React Context being alive
77
64
  private var pendingCallDataMap: Map<String, String>? = null
78
65
 
66
+ // This is the static gate accessible from AcceptCallActivity
79
67
  @JvmStatic
80
68
  fun setPendingCallData(data: Map<String, String>) {
81
69
  pendingCallDataMap = data
@@ -85,7 +73,6 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
85
73
  fun sendEventToJS(eventName: String, params: Any?) {
86
74
  val reactContext = instance?.reactApplicationContext
87
75
 
88
- // Convert params to WritableMap
89
76
  val bridgeData = when (params) {
90
77
  is Map<*, *> -> {
91
78
  val map = Arguments.createMap()
@@ -97,12 +84,15 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
97
84
  else -> null
98
85
  }
99
86
 
87
+ // If app is alive, send via Bridge
100
88
  if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
101
89
  reactContext
102
90
  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
103
91
  ?.emit(eventName, bridgeData)
104
- } else if (eventName == "onCallAccepted" && params is Map<*, *>) {
105
- // FALLBACK: Store data for polling if JS isn't ready
92
+ }
93
+ // If app is in killed state, save for polling
94
+ else if (eventName == "onCallAccepted" && params is Map<*, *>) {
95
+ @Suppress("UNCHECKED_CAST")
106
96
  setPendingCallData(params as Map<String, String>)
107
97
  }
108
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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",