rns-nativecall 0.9.9 → 1.0.1
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.
- package/android/src/main/java/com/rnsnativecall/CallActionReceiver.kt +15 -12
- package/android/src/main/java/com/rnsnativecall/CallForegroundService.kt +12 -23
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +76 -88
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +8 -6
- package/package.json +1 -1
|
@@ -20,32 +20,35 @@ class CallActionReceiver : BroadcastReceiver() {
|
|
|
20
20
|
intent.extras?.get(key)?.let { fullDataMap[key] = it.toString() }
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// --- HANDLE
|
|
24
|
-
if (action == "
|
|
23
|
+
// --- HANDLE ANSWER ---
|
|
24
|
+
if (action == "ACTION_ANSWER") {
|
|
25
|
+
// 1. Kill the ringing notification and service immediately
|
|
25
26
|
NativeCallManager.dismissIncomingCall(context, uuid)
|
|
26
|
-
CallForegroundService.stop(context)
|
|
27
27
|
|
|
28
|
+
// 2. Queue or Send the accept event
|
|
28
29
|
if (CallModule.isReady()) {
|
|
29
|
-
CallModule.sendEventToJS("
|
|
30
|
+
CallModule.sendEventToJS("onCallAccepted", fullDataMap)
|
|
30
31
|
} else {
|
|
31
|
-
CallModule.setPendingCallData("
|
|
32
|
+
CallModule.setPendingCallData("onCallAccepted_pending", fullDataMap)
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
|
|
35
|
+
// 3. Bring the app to the front
|
|
36
|
+
launchApp(context, intent.extras)
|
|
34
37
|
collapseNotificationShade(context)
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
// --- HANDLE
|
|
38
|
-
if (action == "
|
|
40
|
+
// --- HANDLE REJECT ---
|
|
41
|
+
if (action == "ACTION_REJECT") {
|
|
42
|
+
// This kills the Notification AND the Foreground Service (via our updated Manager)
|
|
39
43
|
NativeCallManager.dismissIncomingCall(context, uuid)
|
|
40
44
|
|
|
45
|
+
// Notify JS
|
|
41
46
|
if (CallModule.isReady()) {
|
|
42
|
-
CallModule.sendEventToJS("
|
|
47
|
+
CallModule.sendEventToJS("onCallRejected", fullDataMap)
|
|
43
48
|
} else {
|
|
44
|
-
CallModule.setPendingCallData("
|
|
49
|
+
CallModule.setPendingCallData("onCallRejected_pending", fullDataMap)
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
// This will open your app and the system usually collapses the shade automatically
|
|
48
|
-
launchApp(context, intent.extras)
|
|
49
52
|
collapseNotificationShade(context)
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -18,8 +18,7 @@ class CallForegroundService : Service() {
|
|
|
18
18
|
private var unlockReceiver: UnlockReceiver? = null // Store reference for unregistering
|
|
19
19
|
|
|
20
20
|
companion object {
|
|
21
|
-
private const val
|
|
22
|
-
private const val CHANNEL_ID = "incoming_call_service"
|
|
21
|
+
private const val CHANNEL_ID = "CALL_CHANNEL_V10_URGENT"
|
|
23
22
|
|
|
24
23
|
fun stop(context: Context) {
|
|
25
24
|
val intent = Intent(context, CallForegroundService::class.java)
|
|
@@ -52,6 +51,9 @@ class CallForegroundService : Service() {
|
|
|
52
51
|
): Int {
|
|
53
52
|
val data = intent?.extras
|
|
54
53
|
val name = data?.getString("name") ?: "Someone"
|
|
54
|
+
val uuid = data?.getString("callUuid") ?: "default_uuid"
|
|
55
|
+
|
|
56
|
+
val notificationId = uuid.hashCode()
|
|
55
57
|
|
|
56
58
|
createNotificationChannel()
|
|
57
59
|
|
|
@@ -68,12 +70,12 @@ class CallForegroundService : Service() {
|
|
|
68
70
|
|
|
69
71
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
70
72
|
startForeground(
|
|
71
|
-
|
|
73
|
+
notificationId,
|
|
72
74
|
notification,
|
|
73
75
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL,
|
|
74
76
|
)
|
|
75
77
|
} else {
|
|
76
|
-
startForeground(
|
|
78
|
+
startForeground(notificationId, notification)
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// Launch the Headless Task
|
|
@@ -95,26 +97,13 @@ class CallForegroundService : Service() {
|
|
|
95
97
|
e.printStackTrace()
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
// // Trigger the incoming call notification UI handled by NativeCallManager
|
|
99
|
-
// try {
|
|
100
|
-
// val map = mutableMapOf<String, String>()
|
|
101
|
-
// data?.let { b ->
|
|
102
|
-
// for (key in b.keySet()) {
|
|
103
|
-
// map[key] = b.get(key)?.toString() ?: ""
|
|
104
|
-
// }
|
|
105
|
-
// }
|
|
106
|
-
// NativeCallManager.handleIncomingPush(this, map)
|
|
107
|
-
// } catch (e: Exception) {
|
|
108
|
-
// e.printStackTrace()
|
|
109
|
-
// }
|
|
110
|
-
|
|
111
100
|
// Auto-stop after 30s
|
|
112
|
-
Handler(Looper.getMainLooper()).postDelayed({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}, 30000)
|
|
101
|
+
// Handler(Looper.getMainLooper()).postDelayed({
|
|
102
|
+
// try {
|
|
103
|
+
// stopSelf()
|
|
104
|
+
// } catch (e: Exception) {
|
|
105
|
+
// }
|
|
106
|
+
// }, 30000)
|
|
118
107
|
|
|
119
108
|
return START_NOT_STICKY
|
|
120
109
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
package com.rnsnativecall
|
|
2
2
|
|
|
3
3
|
import android.app.ActivityManager
|
|
4
|
-
import android.app.NotificationManager
|
|
5
4
|
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
6
|
import android.app.PendingIntent
|
|
7
7
|
import android.content.Context
|
|
8
8
|
import android.content.Intent
|
|
@@ -10,14 +10,12 @@ import android.os.Build
|
|
|
10
10
|
import android.os.Bundle
|
|
11
11
|
import androidx.core.app.NotificationCompat
|
|
12
12
|
import androidx.core.content.ContextCompat
|
|
13
|
+
import com.facebook.react.HeadlessJsTaskService
|
|
13
14
|
import com.google.firebase.messaging.FirebaseMessagingService
|
|
14
15
|
import com.google.firebase.messaging.RemoteMessage
|
|
15
|
-
import com.facebook.react.HeadlessJsTaskService
|
|
16
|
-
|
|
17
16
|
import com.rnsnativecall.CallState
|
|
18
17
|
|
|
19
18
|
class CallMessagingService : FirebaseMessagingService() {
|
|
20
|
-
|
|
21
19
|
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
|
22
20
|
val data = remoteMessage.data
|
|
23
21
|
val context = applicationContext
|
|
@@ -26,78 +24,62 @@ class CallMessagingService : FirebaseMessagingService() {
|
|
|
26
24
|
val type = data["type"] ?: ""
|
|
27
25
|
|
|
28
26
|
if (type == "CANCEL") {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (CallState.getCurrent() == uuid) {
|
|
34
|
-
CallState.clear(uuid, context)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
NativeCallManager.dismissIncomingCall(context, uuid)
|
|
38
|
-
showMissedCallNotification(context, data, uuid)
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Inside onMessageReceived
|
|
43
|
-
if (!CallState.setCurrent(uuid)) {
|
|
44
|
-
// We are busy! Start a SILENT headless task to send the WebSocket busy msg
|
|
45
|
-
val busyIntent = Intent(context, CallHeadlessTask::class.java).apply {
|
|
46
|
-
putExtras(Bundle().apply {
|
|
47
|
-
data.forEach { (k, v) -> putString(k, v) }
|
|
48
|
-
putBoolean("isBusySignal", true)
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
context.startService(busyIntent)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
27
|
+
NativeCallManager.stopRingtone()
|
|
28
|
+
// Pass context here to persist the cancellation
|
|
29
|
+
CallState.markCanceled(uuid, context)
|
|
54
30
|
|
|
31
|
+
if (CallState.getCurrent() == uuid) {
|
|
32
|
+
CallState.clear(uuid, context)
|
|
33
|
+
}
|
|
55
34
|
|
|
35
|
+
NativeCallManager.dismissIncomingCall(context, uuid)
|
|
36
|
+
showMissedCallNotification(context, data, uuid)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
56
39
|
|
|
40
|
+
// Inside onMessageReceived
|
|
41
|
+
if (!CallState.setCurrent(uuid)) {
|
|
42
|
+
// We are busy! Start a SILENT headless task to send the WebSocket busy msg
|
|
43
|
+
val busyIntent =
|
|
44
|
+
Intent(context, CallHeadlessTask::class.java).apply {
|
|
45
|
+
putExtras(
|
|
46
|
+
Bundle().apply {
|
|
47
|
+
data.forEach { (k, v) -> putString(k, v) }
|
|
48
|
+
putBoolean("isBusySignal", true)
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
context.startService(busyIntent)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
57
55
|
|
|
58
56
|
if (isAppInForeground(context)) {
|
|
59
57
|
// Foreground → send event directly
|
|
60
58
|
CallModule.sendEventToJS("onCallReceived", data)
|
|
61
59
|
} else {
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// }
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Background → start foreground service (which in turn starts headless)
|
|
82
|
-
val serviceIntent = Intent(context, CallForegroundService::class.java).apply {
|
|
83
|
-
putExtras(Bundle().apply {
|
|
84
|
-
data.forEach { (k, v) -> putString(k, v) }
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
89
|
-
context.startForegroundService(serviceIntent)
|
|
90
|
-
} else {
|
|
91
|
-
context.startService(serviceIntent)
|
|
92
|
-
}
|
|
93
|
-
|
|
60
|
+
// Background → start foreground service (which in turn starts headless)
|
|
61
|
+
val serviceIntent =
|
|
62
|
+
Intent(context, CallForegroundService::class.java).apply {
|
|
63
|
+
putExtra("callUuid", uuid) // Key: Pass the UUID here
|
|
64
|
+
putExtras(
|
|
65
|
+
Bundle().apply {
|
|
66
|
+
data.forEach { (k, v) -> putString(k, v) }
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
72
|
+
context.startForegroundService(serviceIntent)
|
|
73
|
+
} else {
|
|
74
|
+
context.startService(serviceIntent)
|
|
75
|
+
}
|
|
94
76
|
}
|
|
95
77
|
}
|
|
96
78
|
|
|
97
79
|
private fun showMissedCallNotification(
|
|
98
80
|
context: Context,
|
|
99
81
|
data: Map<String, String>,
|
|
100
|
-
uuid: String
|
|
82
|
+
uuid: String,
|
|
101
83
|
) {
|
|
102
84
|
val name = data["name"] ?: "Unknown"
|
|
103
85
|
val callType = data["callType"] ?: "video"
|
|
@@ -107,40 +89,46 @@ if (!CallState.setCurrent(uuid)) {
|
|
|
107
89
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
108
90
|
|
|
109
91
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
110
|
-
val channel =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
val channel =
|
|
93
|
+
NotificationChannel(
|
|
94
|
+
channelId,
|
|
95
|
+
"Missed Calls",
|
|
96
|
+
NotificationManager.IMPORTANCE_DEFAULT,
|
|
97
|
+
).apply { description = "Missed call notifications" }
|
|
115
98
|
notificationManager.createNotificationChannel(channel)
|
|
116
99
|
}
|
|
117
100
|
|
|
118
101
|
val iconResId =
|
|
119
|
-
context.resources
|
|
102
|
+
context.resources
|
|
103
|
+
.getIdentifier("ic_missed_call", "drawable", context.packageName)
|
|
120
104
|
.takeIf { it != 0 } ?: android.R.drawable.sym_call_missed
|
|
121
105
|
val appName = context.applicationInfo.loadLabel(context.packageManager).toString()
|
|
122
106
|
|
|
123
107
|
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
124
108
|
launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
125
|
-
val pendingIntent =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
109
|
+
val pendingIntent =
|
|
110
|
+
PendingIntent.getActivity(
|
|
111
|
+
context,
|
|
112
|
+
uuid.hashCode(),
|
|
113
|
+
launchIntent,
|
|
114
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
115
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
116
|
+
} else {
|
|
117
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
val builder =
|
|
122
|
+
NotificationCompat
|
|
123
|
+
.Builder(context, channelId)
|
|
124
|
+
.setSmallIcon(iconResId)
|
|
125
|
+
.setContentTitle("$appName • Missed $callType call")
|
|
126
|
+
.setContentText("You missed a call from $name")
|
|
127
|
+
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
128
|
+
.setAutoCancel(true)
|
|
129
|
+
.setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
|
|
130
|
+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
131
|
+
.setContentIntent(pendingIntent)
|
|
144
132
|
|
|
145
133
|
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
146
134
|
}
|
|
@@ -152,7 +140,7 @@ if (!CallState.setCurrent(uuid)) {
|
|
|
152
140
|
val packageName = context.packageName
|
|
153
141
|
return appProcesses.any {
|
|
154
142
|
it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
|
|
155
|
-
|
|
143
|
+
it.processName == packageName
|
|
156
144
|
}
|
|
157
145
|
}
|
|
158
146
|
}
|
|
@@ -19,7 +19,7 @@ object NativeCallManager {
|
|
|
19
19
|
// because the system NotificationManager handles the sound.
|
|
20
20
|
|
|
21
21
|
// Incrementing version to V3 to force fresh channel settings on the device
|
|
22
|
-
const val channelId = "
|
|
22
|
+
const val channelId = "CALL_CHANNEL_V10_URGENT"
|
|
23
23
|
private var currentCallData: Map<String, String>? = null
|
|
24
24
|
|
|
25
25
|
fun getCurrentCallData(): Map<String, String>? = currentCallData
|
|
@@ -129,18 +129,21 @@ object NativeCallManager {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/**
|
|
133
|
-
* Stop the ringtone by canceling the notification itself.
|
|
134
|
-
* The system manages the sound, so when the notification dies, the sound dies.
|
|
135
|
-
*/
|
|
136
132
|
fun dismissIncomingCall(
|
|
137
133
|
context: Context,
|
|
138
134
|
uuid: String?,
|
|
139
135
|
) {
|
|
140
136
|
this.currentCallData = null
|
|
141
137
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
138
|
+
|
|
142
139
|
if (uuid != null) {
|
|
140
|
+
// 1. Kill the notification UI
|
|
143
141
|
notificationManager.cancel(uuid.hashCode())
|
|
142
|
+
|
|
143
|
+
// 2. IMPORTANT: Stop the Foreground Service process
|
|
144
|
+
// This ensures the "Pill" in the status bar goes away
|
|
145
|
+
val serviceIntent = Intent(context, CallForegroundService::class.java)
|
|
146
|
+
context.stopService(serviceIntent)
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
|
|
@@ -183,7 +186,6 @@ object NativeCallManager {
|
|
|
183
186
|
notificationManager.notify(uuid.hashCode(), builder.build())
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
// Deprecated manual method - kept for signature compatibility but does nothing
|
|
187
189
|
fun stopRingtone() {
|
|
188
190
|
// No-op: Sound is now managed by the Notification Channel life-cycle
|
|
189
191
|
}
|