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
|
-
//
|
|
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,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
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
it.destroy()
|
|
68
|
-
MyConnectionService.removeConnection(uuid)
|
|
69
|
-
}
|
|
54
|
+
@ReactMethod
|
|
55
|
+
fun checkTelecomPermissions(promise: Promise) {
|
|
56
|
+
promise.resolve(true)
|
|
70
57
|
}
|
|
71
58
|
|
|
72
|
-
|
|
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
|
-
}
|
|
105
|
-
|
|
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