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.
@@ -20,32 +20,35 @@ class CallActionReceiver : BroadcastReceiver() {
20
20
  intent.extras?.get(key)?.let { fullDataMap[key] = it.toString() }
21
21
  }
22
22
 
23
- // --- HANDLE REJECT ---
24
- if (action == "ACTION_REJECT") {
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("onCallRejected", fullDataMap)
30
+ CallModule.sendEventToJS("onCallAccepted", fullDataMap)
30
31
  } else {
31
- CallModule.setPendingCallData("onCallRejected_pending", fullDataMap)
32
+ CallModule.setPendingCallData("onCallAccepted_pending", fullDataMap)
32
33
  }
33
- // Just close the shade
34
+
35
+ // 3. Bring the app to the front
36
+ launchApp(context, intent.extras)
34
37
  collapseNotificationShade(context)
35
38
  }
36
39
 
37
- // --- HANDLE ANSWER ---
38
- if (action == "ACTION_ANSWER") {
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("onCallAccepted", fullDataMap)
47
+ CallModule.sendEventToJS("onCallRejected", fullDataMap)
43
48
  } else {
44
- CallModule.setPendingCallData("onCallAccepted_pending", fullDataMap)
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 NOTIFICATION_ID = 101
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
- NOTIFICATION_ID,
73
+ notificationId,
72
74
  notification,
73
75
  ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL,
74
76
  )
75
77
  } else {
76
- startForeground(NOTIFICATION_ID, notification)
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
- try {
114
- stopSelf()
115
- } catch (e: Exception) {
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
- NativeCallManager.stopRingtone()
30
- // Pass context here to persist the cancellation
31
- CallState.markCanceled(uuid, context)
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
- // Show incoming call notification instantly
63
- //NativeCallManager.handleIncomingPush(context, data) /// i will use headless instead
64
- // Background → start foreground service + headless task
65
- // val serviceIntent = Intent(context, CallForegroundService::class.java).apply {
66
- // putExtras(Bundle().apply { data.forEach { (k, v) -> putString(k, v) } })
67
- // }
68
- // ContextCompat.startForegroundService(context, serviceIntent)
69
-
70
- // val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
71
- // putExtras(Bundle().apply { data.forEach { (k, v) -> putString(k, v) } })
72
- // }
73
- // try {
74
- // context.startService(headlessIntent)
75
- // HeadlessJsTaskService.acquireWakeLockNow(context)
76
- // } catch (e: Exception) {
77
- // e.printStackTrace()
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 = NotificationChannel(
111
- channelId,
112
- "Missed Calls",
113
- NotificationManager.IMPORTANCE_DEFAULT
114
- ).apply { description = "Missed call notifications" }
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.getIdentifier("ic_missed_call", "drawable", context.packageName)
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 = PendingIntent.getActivity(
126
- context,
127
- uuid.hashCode(),
128
- launchIntent,
129
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
130
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
131
- else
132
- PendingIntent.FLAG_UPDATE_CURRENT
133
- )
134
-
135
- val builder = NotificationCompat.Builder(context, channelId)
136
- .setSmallIcon(iconResId)
137
- .setContentTitle("$appName Missed $callType call")
138
- .setContentText("You missed a call from $name")
139
- .setPriority(NotificationCompat.PRIORITY_HIGH)
140
- .setAutoCancel(true)
141
- .setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
142
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
143
- .setContentIntent(pendingIntent)
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
- it.processName == packageName
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 = "CALL_CHANNEL_V6_URGENT"
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.9.9",
3
+ "version": "1.0.1",
4
4
  "description": "High-performance React Native module for handling native VoIP call UI on Android and iOS.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",