rns-nativecall 1.0.6 → 1.0.8

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.
@@ -2,20 +2,25 @@ package com.rnsnativecall
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.Intent
5
+ import android.os.Build
5
6
  import android.os.Bundle
6
7
  import android.view.WindowManager
7
- import android.os.Build
8
8
 
9
9
  class AcceptCallActivity : Activity() {
10
10
  override fun onCreate(savedInstanceState: Bundle?) {
11
11
  super.onCreate(savedInstanceState)
12
-
12
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
13
+ setShowWhenLocked(true)
14
+ setTurnScreenOn(true)
15
+ }
13
16
  val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
14
17
  if (launchIntent != null) {
15
18
  launchIntent.apply {
16
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
17
- Intent.FLAG_ACTIVITY_SINGLE_TOP or
18
- Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
19
+ addFlags(
20
+ Intent.FLAG_ACTIVITY_NEW_TASK or
21
+ Intent.FLAG_ACTIVITY_SINGLE_TOP or
22
+ Intent.FLAG_ACTIVITY_REORDER_TO_FRONT,
23
+ )
19
24
  putExtras(intent.extras ?: Bundle())
20
25
  putExtra("navigatingToCall", true)
21
26
  }
@@ -25,4 +30,4 @@ class AcceptCallActivity : Activity() {
25
30
  finish()
26
31
  overridePendingTransition(0, 0)
27
32
  }
28
- }
33
+ }
@@ -1,7 +1,6 @@
1
1
  package com.rnsnativecall
2
2
 
3
3
  import android.app.*
4
- import android.content.BroadcastReceiver
5
4
  import android.content.Context
6
5
  import android.content.Intent
7
6
  import android.content.IntentFilter
@@ -15,29 +14,19 @@ import androidx.core.app.NotificationCompat
15
14
  import com.facebook.react.HeadlessJsTaskService
16
15
 
17
16
  class CallForegroundService : Service() {
18
- private var unlockReceiver: UnlockReceiver? = null // Store reference for unregistering
17
+ private var unlockReceiver: UnlockReceiver? = null
19
18
 
20
19
  companion object {
21
- private const val CHANNEL_ID = "CALL_CHANNEL_V14_URGENT"
22
-
23
20
  fun stop(context: Context) {
24
- val intent = Intent(context, CallForegroundService::class.java)
25
- context.stopService(intent)
21
+ context.stopService(Intent(context, CallForegroundService::class.java))
26
22
  }
27
23
  }
28
24
 
29
25
  override fun onCreate() {
30
26
  super.onCreate()
31
-
32
- // --- DYNAMIC REGISTRATION FIX ---
33
- // Registering here makes the system allow the USER_PRESENT broadcast
34
- // because the app is already in the Foreground (via this service).
35
27
  unlockReceiver = UnlockReceiver()
36
28
  val filter = IntentFilter(Intent.ACTION_USER_PRESENT)
37
-
38
29
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
39
- // Prefer NOT_EXPORTED for dynamically-registered receivers to avoid
40
- // background delivery issues and to restrict broadcasts to our process.
41
30
  registerReceiver(unlockReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
42
31
  } else {
43
32
  registerReceiver(unlockReceiver, filter)
@@ -50,43 +39,10 @@ class CallForegroundService : Service() {
50
39
  startId: Int,
51
40
  ): Int {
52
41
  val data = intent?.extras
53
- val name = data?.getString("name") ?: "Someone"
54
- val uuid = data?.getString("callUuid") ?: "default_uuid"
55
-
56
- val notificationId = uuid.hashCode()
57
-
58
- createNotificationChannel()
59
-
60
- val notification =
61
- NotificationCompat
62
- .Builder(this, CHANNEL_ID)
63
- .setContentTitle(name)
64
- .setContentText("Connecting...")
65
- .setSmallIcon(applicationInfo.icon)
66
- .setPriority(NotificationCompat.PRIORITY_DEFAULT)
67
- .setCategory(NotificationCompat.CATEGORY_SERVICE)
68
- .setOngoing(true)
69
- .build()
70
-
71
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
72
- startForeground(
73
- notificationId,
74
- notification,
75
- ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL,
76
- )
77
- } else {
78
- startForeground(notificationId, notification)
79
- }
80
-
81
- // Launch the Headless Task
82
42
  val headlessIntent =
83
43
  Intent(this, CallHeadlessTask::class.java).apply {
84
44
  val bundle = Bundle()
85
- data?.let { b ->
86
- for (key in b.keySet()) {
87
- bundle.putString(key, b.get(key)?.toString())
88
- }
89
- }
45
+ data?.let { b -> for (key in b.keySet()) bundle.putString(key, b.get(key)?.toString()) }
90
46
  putExtras(bundle)
91
47
  }
92
48
 
@@ -97,50 +53,41 @@ class CallForegroundService : Service() {
97
53
  e.printStackTrace()
98
54
  }
99
55
 
100
- // Auto-stop after 30s
101
- // Handler(Looper.getMainLooper()).postDelayed({
102
- // try {
103
- // stopSelf()
104
- // } catch (e: Exception) {
105
- // }
106
- // }, 30000)
107
-
56
+ Handler(Looper.getMainLooper()).postDelayed({ stopSelf() }, 30000)
108
57
  return START_NOT_STICKY
109
58
  }
110
59
 
111
60
  override fun onDestroy() {
112
- // --- CLEANUP ---
113
- // Unregister to prevent memory leaks once the call ends or service stops
114
61
  try {
115
62
  unlockReceiver?.let { unregisterReceiver(it) }
116
63
  } catch (e: Exception) {
117
- e.printStackTrace()
118
- }
119
-
120
- try {
121
- stopForeground(true)
122
- } catch (_: Exception) {
123
64
  }
124
65
  super.onDestroy()
125
66
  }
126
67
 
127
- private fun createNotificationChannel() {
128
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
129
- val channel =
130
- NotificationChannel(
131
- CHANNEL_ID,
132
- "Call Service",
133
- NotificationManager.IMPORTANCE_DEFAULT,
134
- ).apply {
135
- description = "Incoming call connection state"
136
- setSound(null, null)
137
- enableVibration(false)
138
- setBypassDnd(true)
139
- lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
140
- }
141
- val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
142
- manager.createNotificationChannel(channel)
68
+ override fun onBind(intent: Intent?): IBinder? = null
69
+ }
70
+
71
+ class CallUiForegroundService : Service() {
72
+ override fun onStartCommand(
73
+ intent: Intent?,
74
+ flags: Int,
75
+ startId: Int,
76
+ ): Int {
77
+ val notification = NativeCallManager.pendingCallNotification ?: return START_NOT_STICKY
78
+ val id = NativeCallManager.pendingNotificationId ?: return START_NOT_STICKY
79
+
80
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
81
+ startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL)
82
+ } else {
83
+ startForeground(id, notification)
143
84
  }
85
+ return START_NOT_STICKY
86
+ }
87
+
88
+ override fun onDestroy() {
89
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) stopForeground(STOP_FOREGROUND_REMOVE)
90
+ super.onDestroy()
144
91
  }
145
92
 
146
93
  override fun onBind(intent: Intent?): IBinder? = null
@@ -57,10 +57,10 @@ class CallMessagingService : FirebaseMessagingService() {
57
57
  // Foreground → send event directly
58
58
  CallModule.sendEventToJS("onCallReceived", data)
59
59
  } else {
60
- // Background → start foreground service (which in turn starts headless)
60
+ // Background → start the SILENT wake service
61
61
  val serviceIntent =
62
62
  Intent(context, CallForegroundService::class.java).apply {
63
- putExtra("callUuid", uuid) // Key: Pass the UUID here
63
+ putExtra("callUuid", uuid)
64
64
  putExtras(
65
65
  Bundle().apply {
66
66
  data.forEach { (k, v) -> putString(k, v) }
@@ -68,11 +68,9 @@ class CallMessagingService : FirebaseMessagingService() {
68
68
  )
69
69
  }
70
70
 
71
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
72
- context.startForegroundService(serviceIntent)
73
- } else {
74
- context.startService(serviceIntent)
75
- }
71
+ // FIX: Always use startService here now.
72
+ // We are no longer making this a "Foreground Service" at the wake stage.
73
+ context.startService(serviceIntent)
76
74
  }
77
75
  }
78
76
 
@@ -1,8 +1,6 @@
1
1
  package com.rnsnativecall
2
2
 
3
- import android.app.NotificationChannel
4
- import android.app.NotificationManager
5
- import android.app.PendingIntent
3
+ import android.app.*
6
4
  import android.content.Context
7
5
  import android.content.Intent
8
6
  import android.graphics.Color
@@ -13,12 +11,15 @@ import android.os.Handler
13
11
  import android.os.Looper
14
12
  import androidx.core.app.NotificationCompat
15
13
  import androidx.core.app.Person
14
+ import androidx.core.content.ContextCompat
16
15
 
17
16
  object NativeCallManager {
18
- const val channelId = "CALL_CHANNEL_V14_URGENT"
17
+ const val channelId = "CALL_CHANNEL_V15_URGENT"
19
18
  private var currentCallData: Map<String, String>? = null
20
19
 
21
- fun getCurrentCallData(): Map<String, String>? = currentCallData
20
+ @JvmStatic internal var pendingCallNotification: Notification? = null
21
+
22
+ @JvmStatic internal var pendingNotificationId: Int? = null
22
23
 
23
24
  fun handleIncomingPush(
24
25
  context: Context,
@@ -27,7 +28,6 @@ object NativeCallManager {
27
28
  Handler(Looper.getMainLooper()).post {
28
29
  val uuid = data["callUuid"] ?: return@post
29
30
  this.currentCallData = data
30
-
31
31
  val name = data["name"] ?: "Incoming Call"
32
32
  val notificationId = uuid.hashCode()
33
33
  val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
@@ -39,15 +39,7 @@ object NativeCallManager {
39
39
  PendingIntent.FLAG_UPDATE_CURRENT
40
40
  }
41
41
 
42
- // 1. Content Intent (Tap notification)
43
- val noOpIntent =
44
- Intent("com.rnsnativecall.ACTION_NOTIFICATION_TAP_NOOP").apply {
45
- `package` = context.packageName
46
- putExtra("EXTRA_CALL_UUID", uuid)
47
- }
48
- val contentIntent = PendingIntent.getBroadcast(context, notificationId + 3, noOpIntent, pendingFlags)
49
-
50
- // 2. Full Screen Intent (Wake lockscreen)
42
+ // Intents
51
43
  val overlayIntent =
52
44
  Intent(context, NotificationOverlayActivity::class.java).apply {
53
45
  putExtra("EXTRA_CALL_UUID", uuid)
@@ -56,7 +48,6 @@ object NativeCallManager {
56
48
  }
57
49
  val fullScreenPendingIntent = PendingIntent.getActivity(context, notificationId, overlayIntent, pendingFlags)
58
50
 
59
- // 3. Answer Action
60
51
  val answerIntent =
61
52
  Intent(context, CallActionReceiver::class.java).apply {
62
53
  action = "ACTION_ANSWER"
@@ -65,7 +56,6 @@ object NativeCallManager {
65
56
  }
66
57
  val answerPendingIntent = PendingIntent.getBroadcast(context, notificationId + 1, answerIntent, pendingFlags)
67
58
 
68
- // 4. Reject Action
69
59
  val rejectIntent =
70
60
  Intent(context, CallActionReceiver::class.java).apply {
71
61
  action = "ACTION_REJECT"
@@ -74,15 +64,14 @@ object NativeCallManager {
74
64
  }
75
65
  val rejectPendingIntent = PendingIntent.getBroadcast(context, notificationId + 2, rejectIntent, pendingFlags)
76
66
 
77
- // Setup Channel with System-Managed Sound
67
+ // Channel
78
68
  val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
79
69
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
80
70
  val channel =
81
71
  NotificationChannel(channelId, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
82
- lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
72
+ lockscreenVisibility = Notification.VISIBILITY_PUBLIC
83
73
  enableVibration(true)
84
74
  setBypassDnd(true)
85
- // System-managed sound ensures the "pill" pops up correctly
86
75
  setSound(
87
76
  ringtoneUri,
88
77
  AudioAttributes
@@ -109,80 +98,31 @@ object NativeCallManager {
109
98
  .setPriority(NotificationCompat.PRIORITY_MAX)
110
99
  .setCategory(NotificationCompat.CATEGORY_CALL)
111
100
  .setOngoing(true)
112
- .setAutoCancel(false)
113
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
114
- .setContentIntent(contentIntent)
115
101
  .setFullScreenIntent(fullScreenPendingIntent, true)
116
- .setStyle(
117
- NotificationCompat.CallStyle
118
- .forIncomingCall(caller, rejectPendingIntent, answerPendingIntent)
119
- .setAnswerButtonColorHint(Color.GREEN)
120
- .setDeclineButtonColorHint(Color.RED),
121
- ).setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
122
-
123
- // Sending this notification will now trigger the system ringtone automatically
124
- notificationManager.notify(notificationId, builder.build())
125
- }
126
- }
102
+ .setStyle(NotificationCompat.CallStyle.forIncomingCall(caller, rejectPendingIntent, answerPendingIntent))
103
+ .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
127
104
 
128
- fun dismissIncomingCall(
129
- context: Context,
130
- uuid: String?,
131
- ) {
132
- this.currentCallData = null
133
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
105
+ val notification = builder.build()
106
+ pendingCallNotification = notification
107
+ pendingNotificationId = notificationId
134
108
 
135
- if (uuid != null) {
136
- // 1. Kill the notification UI
137
- notificationManager.cancel(uuid.hashCode())
109
+ // 1. Start the LOUD UI Service (Google allows this for PHONE_CALL)
110
+ val uiIntent = Intent(context, CallUiForegroundService::class.java)
111
+ ContextCompat.startForegroundService(context, uiIntent)
138
112
 
139
- // 2. IMPORTANT: Stop the Foreground Service process
140
- // This ensures the "Pill" in the status bar goes away
141
- val serviceIntent = Intent(context, CallForegroundService::class.java)
142
- context.stopService(serviceIntent)
113
+ // 2. Stop the SILENT Wake service (which no longer needs DATA_SYNC)
114
+ CallForegroundService.stop(context)
143
115
  }
144
116
  }
145
117
 
146
- fun connecting(
147
- context: Context,
148
- uuid: String,
149
- name: String,
150
- callType: String,
151
- ) {
152
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
153
- val builder =
154
- NotificationCompat
155
- .Builder(context, channelId)
156
- .setSmallIcon(context.applicationInfo.icon)
157
- .setContentTitle(name)
158
- .setContentText("Connecting…")
159
- .setPriority(NotificationCompat.PRIORITY_MAX)
160
- .setCategory(NotificationCompat.CATEGORY_CALL)
161
- .setOngoing(true)
162
- .setStyle(NotificationCompat.BigTextStyle().bigText("Connecting…"))
163
- notificationManager.notify(uuid.hashCode(), builder.build())
164
- }
165
-
166
- fun aborting(
118
+ fun dismissIncomingCall(
167
119
  context: Context,
168
- uuid: String,
169
- name: String,
170
- callType: String,
120
+ uuid: String?,
171
121
  ) {
172
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
173
- val builder =
174
- NotificationCompat
175
- .Builder(context, channelId)
176
- .setSmallIcon(context.applicationInfo.icon)
177
- .setContentTitle(name)
178
- .setContentText("Aborting…")
179
- .setPriority(NotificationCompat.PRIORITY_MAX)
180
- .setCategory(NotificationCompat.CATEGORY_CALL)
181
- .setOngoing(true)
182
- notificationManager.notify(uuid.hashCode(), builder.build())
183
- }
184
-
185
- fun stopRingtone() {
186
- // No-op: Sound is now managed by the Notification Channel life-cycle
122
+ pendingCallNotification = null
123
+ pendingNotificationId = null
124
+ val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
125
+ uuid?.let { manager.cancel(it.hashCode()) }
126
+ context.stopService(Intent(context, CallUiForegroundService::class.java))
187
127
  }
188
128
  }
@@ -1,6 +1,8 @@
1
1
  package com.rnsnativecall
2
2
 
3
3
  import android.app.Activity
4
+ import android.app.KeyguardManager
5
+ import android.content.Context
4
6
  import android.os.Build
5
7
  import android.os.Bundle
6
8
  import android.view.WindowManager
@@ -10,17 +12,21 @@ class NotificationOverlayActivity : Activity() {
10
12
  super.onCreate(savedInstanceState)
11
13
 
12
14
  // These flags allow the notification pill to show over the lockscreen
15
+
13
16
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
14
17
  setShowWhenLocked(true)
15
18
  setTurnScreenOn(true)
19
+ val km = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
20
+ km.requestDismissKeyguard(this, null)
16
21
  } else {
17
22
  @Suppress("DEPRECATION")
18
23
  window.addFlags(
19
24
  WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
20
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
21
- WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
25
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
26
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
27
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD,
22
28
  )
23
29
  }
24
30
  finish()
25
31
  }
26
- }
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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",
@@ -86,9 +86,9 @@ function withAndroidConfig(config) {
86
86
  const permissions = [
87
87
  'android.permission.VIBRATE',
88
88
  'android.permission.FOREGROUND_SERVICE',
89
- 'android.permission.FOREGROUND_SERVICE_PHONE_CALL', // ADDED THIS
90
- 'android.permission.USE_FULL_SCREEN_INTENT', // ADDED THIS
91
- 'android.permission.MANAGE_ONGOING_CALLS', // ADDED THIS
89
+ 'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
90
+ 'android.permission.USE_FULL_SCREEN_INTENT',
91
+ 'android.permission.MANAGE_ONGOING_CALLS',
92
92
  'android.permission.POST_NOTIFICATIONS',
93
93
  'android.permission.WAKE_LOCK',
94
94
  'android.permission.DISABLE_KEYGUARD',
@@ -107,7 +107,8 @@ function withAndroidConfig(config) {
107
107
 
108
108
  const services = [
109
109
  { name: 'com.rnsnativecall.CallMessagingService', exported: 'false', filter: 'com.google.firebase.MESSAGING_EVENT' },
110
- { name: 'com.rnsnativecall.CallForegroundService', type: 'phoneCall' },
110
+ { name: 'com.rnsnativecall.CallForegroundService' },
111
+ { name: 'com.rnsnativecall.CallUiForegroundService', type: 'phoneCall' },
111
112
  { name: 'com.rnsnativecall.CallHeadlessTask' }
112
113
  ];
113
114