rns-nativecall 1.0.7 → 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,8 +1,6 @@
1
1
  package com.rnsnativecall
2
2
 
3
3
  import android.app.*
4
- import android.app.Service
5
- import android.content.BroadcastReceiver
6
4
  import android.content.Context
7
5
  import android.content.Intent
8
6
  import android.content.IntentFilter
@@ -13,33 +11,22 @@ import android.os.Handler
13
11
  import android.os.IBinder
14
12
  import android.os.Looper
15
13
  import androidx.core.app.NotificationCompat
16
- import androidx.core.content.ContextCompat
17
14
  import com.facebook.react.HeadlessJsTaskService
18
15
 
19
16
  class CallForegroundService : Service() {
20
- private var unlockReceiver: UnlockReceiver? = null // Store reference for unregistering
17
+ private var unlockReceiver: UnlockReceiver? = null
21
18
 
22
19
  companion object {
23
- private const val CHANNEL_ID = "CALL_WAKE_SILENT"
24
-
25
20
  fun stop(context: Context) {
26
- val intent = Intent(context, CallForegroundService::class.java)
27
- context.stopService(intent)
21
+ context.stopService(Intent(context, CallForegroundService::class.java))
28
22
  }
29
23
  }
30
24
 
31
25
  override fun onCreate() {
32
26
  super.onCreate()
33
-
34
- // --- DYNAMIC REGISTRATION FIX ---
35
- // Registering here makes the system allow the USER_PRESENT broadcast
36
- // because the app is already in the Foreground (via this service).
37
27
  unlockReceiver = UnlockReceiver()
38
28
  val filter = IntentFilter(Intent.ACTION_USER_PRESENT)
39
-
40
29
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
41
- // Prefer NOT_EXPORTED for dynamically-registered receivers to avoid
42
- // background delivery issues and to restrict broadcasts to our process.
43
30
  registerReceiver(unlockReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
44
31
  } else {
45
32
  registerReceiver(unlockReceiver, filter)
@@ -52,43 +39,10 @@ class CallForegroundService : Service() {
52
39
  startId: Int,
53
40
  ): Int {
54
41
  val data = intent?.extras
55
- val name = data?.getString("name") ?: "Someone"
56
- val uuid = data?.getString("callUuid") ?: "default_uuid"
57
-
58
- val notificationId = uuid.hashCode()
59
-
60
- createNotificationChannel()
61
-
62
- val notification =
63
- NotificationCompat
64
- .Builder(this, CHANNEL_ID)
65
- .setContentTitle(name)
66
- .setContentText("Connecting...")
67
- .setSmallIcon(applicationInfo.icon)
68
- .setPriority(NotificationCompat.PRIORITY_LOW)
69
- .setCategory(NotificationCompat.CATEGORY_SERVICE)
70
- .setOngoing(true)
71
- .build()
72
-
73
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
74
- startForeground(
75
- notificationId,
76
- notification,
77
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
78
- )
79
- } else {
80
- startForeground(notificationId, notification)
81
- }
82
-
83
- // Launch the Headless Task
84
42
  val headlessIntent =
85
43
  Intent(this, CallHeadlessTask::class.java).apply {
86
44
  val bundle = Bundle()
87
- data?.let { b ->
88
- for (key in b.keySet()) {
89
- bundle.putString(key, b.get(key)?.toString())
90
- }
91
- }
45
+ data?.let { b -> for (key in b.keySet()) bundle.putString(key, b.get(key)?.toString()) }
92
46
  putExtras(bundle)
93
47
  }
94
48
 
@@ -99,52 +53,18 @@ class CallForegroundService : Service() {
99
53
  e.printStackTrace()
100
54
  }
101
55
 
102
- // / Auto - stop after 30 s
103
- Handler(Looper.getMainLooper()).postDelayed({
104
- try {
105
- stopSelf()
106
- } catch (e: Exception) {
107
- }
108
- }, 30000)
109
-
56
+ Handler(Looper.getMainLooper()).postDelayed({ stopSelf() }, 30000)
110
57
  return START_NOT_STICKY
111
58
  }
112
59
 
113
60
  override fun onDestroy() {
114
- // --- CLEANUP ---
115
- // Unregister to prevent memory leaks once the call ends or service stops
116
61
  try {
117
62
  unlockReceiver?.let { unregisterReceiver(it) }
118
63
  } catch (e: Exception) {
119
- e.printStackTrace()
120
- }
121
-
122
- try {
123
- stopForeground(true)
124
- } catch (_: Exception) {
125
64
  }
126
65
  super.onDestroy()
127
66
  }
128
67
 
129
- private fun createNotificationChannel() {
130
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
131
- val channel =
132
- NotificationChannel(
133
- CHANNEL_ID,
134
- "Call Service",
135
- NotificationManager.IMPORTANCE_LOW,
136
- ).apply {
137
- description = "Incoming call connection state"
138
- setSound(null, null)
139
- enableVibration(false)
140
- setBypassDnd(false)
141
- lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
142
- }
143
- val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
144
- manager.createNotificationChannel(channel)
145
- }
146
- }
147
-
148
68
  override fun onBind(intent: Intent?): IBinder? = null
149
69
  }
150
70
 
@@ -154,36 +74,19 @@ class CallUiForegroundService : Service() {
154
74
  flags: Int,
155
75
  startId: Int,
156
76
  ): Int {
157
- val notification =
158
- NativeCallManager.pendingCallNotification
159
- ?: run {
160
- stopSelf()
161
- return START_NOT_STICKY
162
- }
77
+ val notification = NativeCallManager.pendingCallNotification ?: return START_NOT_STICKY
78
+ val id = NativeCallManager.pendingNotificationId ?: return START_NOT_STICKY
163
79
 
164
- val notificationId =
165
- NativeCallManager.pendingNotificationId
166
- ?: run {
167
- stopSelf()
168
- return START_NOT_STICKY
169
- }
170
-
171
- // Inside CallUiForegroundService.onStartCommand
172
80
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
173
- startForeground(
174
- notificationId,
175
- notification,
176
- ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL,
177
- )
81
+ startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL)
178
82
  } else {
179
- startForeground(notificationId, notification)
83
+ startForeground(id, notification)
180
84
  }
181
-
182
85
  return START_NOT_STICKY
183
86
  }
184
87
 
185
88
  override fun onDestroy() {
186
- stopForeground(true)
89
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) stopForeground(STOP_FOREGROUND_REMOVE)
187
90
  super.onDestroy()
188
91
  }
189
92
 
@@ -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,17 +1,12 @@
1
1
  package com.rnsnativecall
2
2
 
3
- import android.app.Notification
4
- import android.app.NotificationChannel
5
- import android.app.NotificationManager
6
- import android.app.PendingIntent
3
+ import android.app.*
7
4
  import android.content.Context
8
5
  import android.content.Intent
9
- import android.content.pm.ServiceInfo
10
6
  import android.graphics.Color
11
7
  import android.media.AudioAttributes
12
8
  import android.media.RingtoneManager
13
9
  import android.os.Build
14
- import android.os.Bundle
15
10
  import android.os.Handler
16
11
  import android.os.Looper
17
12
  import androidx.core.app.NotificationCompat
@@ -22,13 +17,10 @@ object NativeCallManager {
22
17
  const val channelId = "CALL_CHANNEL_V15_URGENT"
23
18
  private var currentCallData: Map<String, String>? = null
24
19
 
25
- // Change these to internal so the Service can access them
26
- @JvmStatic internal var pendingCallNotification: android.app.Notification? = null
20
+ @JvmStatic internal var pendingCallNotification: Notification? = null
27
21
 
28
22
  @JvmStatic internal var pendingNotificationId: Int? = null
29
23
 
30
- fun getCurrentCallData(): Map<String, String>? = currentCallData
31
-
32
24
  fun handleIncomingPush(
33
25
  context: Context,
34
26
  data: Map<String, String>,
@@ -36,7 +28,6 @@ object NativeCallManager {
36
28
  Handler(Looper.getMainLooper()).post {
37
29
  val uuid = data["callUuid"] ?: return@post
38
30
  this.currentCallData = data
39
-
40
31
  val name = data["name"] ?: "Incoming Call"
41
32
  val notificationId = uuid.hashCode()
42
33
  val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
@@ -48,15 +39,7 @@ object NativeCallManager {
48
39
  PendingIntent.FLAG_UPDATE_CURRENT
49
40
  }
50
41
 
51
- // 1. Content Intent (Tap notification)
52
- val noOpIntent =
53
- Intent("com.rnsnativecall.ACTION_NOTIFICATION_TAP_NOOP").apply {
54
- `package` = context.packageName
55
- putExtra("EXTRA_CALL_UUID", uuid)
56
- }
57
- val contentIntent = PendingIntent.getBroadcast(context, notificationId + 3, noOpIntent, pendingFlags)
58
-
59
- // 2. Full Screen Intent (Wake lockscreen)
42
+ // Intents
60
43
  val overlayIntent =
61
44
  Intent(context, NotificationOverlayActivity::class.java).apply {
62
45
  putExtra("EXTRA_CALL_UUID", uuid)
@@ -65,7 +48,6 @@ object NativeCallManager {
65
48
  }
66
49
  val fullScreenPendingIntent = PendingIntent.getActivity(context, notificationId, overlayIntent, pendingFlags)
67
50
 
68
- // 3. Answer Action
69
51
  val answerIntent =
70
52
  Intent(context, CallActionReceiver::class.java).apply {
71
53
  action = "ACTION_ANSWER"
@@ -74,7 +56,6 @@ object NativeCallManager {
74
56
  }
75
57
  val answerPendingIntent = PendingIntent.getBroadcast(context, notificationId + 1, answerIntent, pendingFlags)
76
58
 
77
- // 4. Reject Action
78
59
  val rejectIntent =
79
60
  Intent(context, CallActionReceiver::class.java).apply {
80
61
  action = "ACTION_REJECT"
@@ -83,24 +64,25 @@ object NativeCallManager {
83
64
  }
84
65
  val rejectPendingIntent = PendingIntent.getBroadcast(context, notificationId + 2, rejectIntent, pendingFlags)
85
66
 
86
- // Setup Channel with System-Managed Sound
67
+ // Channel
87
68
  val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
88
-
89
- val channel =
90
- NotificationChannel(channelId, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
91
- lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
92
- enableVibration(true)
93
- setBypassDnd(true)
94
- setSound(
95
- ringtoneUri,
96
- AudioAttributes
97
- .Builder()
98
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
99
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
100
- .build(),
101
- )
102
- }
103
- notificationManager.createNotificationChannel(channel)
69
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
70
+ val channel =
71
+ NotificationChannel(channelId, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
72
+ lockscreenVisibility = Notification.VISIBILITY_PUBLIC
73
+ enableVibration(true)
74
+ setBypassDnd(true)
75
+ setSound(
76
+ ringtoneUri,
77
+ AudioAttributes
78
+ .Builder()
79
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
80
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
81
+ .build(),
82
+ )
83
+ }
84
+ notificationManager.createNotificationChannel(channel)
85
+ }
104
86
 
105
87
  val caller =
106
88
  Person
@@ -116,31 +98,19 @@ object NativeCallManager {
116
98
  .setPriority(NotificationCompat.PRIORITY_MAX)
117
99
  .setCategory(NotificationCompat.CATEGORY_CALL)
118
100
  .setOngoing(true)
119
- .setAutoCancel(false)
120
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
121
- .setWhen(0)
122
- .setUsesChronometer(false)
123
- .setContentIntent(contentIntent)
124
101
  .setFullScreenIntent(fullScreenPendingIntent, true)
125
- .setStyle(
126
- NotificationCompat.CallStyle
127
- .forIncomingCall(caller, rejectPendingIntent, answerPendingIntent)
128
- .setAnswerButtonColorHint(Color.GREEN)
129
- .setDeclineButtonColorHint(Color.RED),
130
- ).setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
102
+ .setStyle(NotificationCompat.CallStyle.forIncomingCall(caller, rejectPendingIntent, answerPendingIntent))
103
+ .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
131
104
 
132
105
  val notification = builder.build()
133
106
  pendingCallNotification = notification
134
107
  pendingNotificationId = notificationId
135
108
 
136
- // Relay to UI Service
109
+ // 1. Start the LOUD UI Service (Google allows this for PHONE_CALL)
137
110
  val uiIntent = Intent(context, CallUiForegroundService::class.java)
138
111
  ContextCompat.startForegroundService(context, uiIntent)
139
112
 
140
- // Manual notify ensures the banner pops immediately
141
- val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
142
- manager.notify(notificationId, notification)
143
-
113
+ // 2. Stop the SILENT Wake service (which no longer needs DATA_SYNC)
144
114
  CallForegroundService.stop(context)
145
115
  }
146
116
  }
@@ -151,55 +121,8 @@ object NativeCallManager {
151
121
  ) {
152
122
  pendingCallNotification = null
153
123
  pendingNotificationId = null
154
-
155
- val notificationManager =
156
- context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
157
-
158
- uuid?.let { notificationManager.cancel(it.hashCode()) }
159
-
124
+ val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
125
+ uuid?.let { manager.cancel(it.hashCode()) }
160
126
  context.stopService(Intent(context, CallUiForegroundService::class.java))
161
127
  }
162
-
163
- fun connecting(
164
- context: Context,
165
- uuid: String,
166
- name: String,
167
- callType: String,
168
- ) {
169
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
170
- val builder =
171
- NotificationCompat
172
- .Builder(context, channelId)
173
- .setSmallIcon(context.applicationInfo.icon)
174
- .setContentTitle(name)
175
- .setContentText("Connecting…")
176
- .setPriority(NotificationCompat.PRIORITY_MAX)
177
- .setCategory(NotificationCompat.CATEGORY_CALL)
178
- .setOngoing(true)
179
- .setStyle(NotificationCompat.BigTextStyle().bigText("Connecting…"))
180
- notificationManager.notify(uuid.hashCode(), builder.build())
181
- }
182
-
183
- fun aborting(
184
- context: Context,
185
- uuid: String,
186
- name: String,
187
- callType: String,
188
- ) {
189
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
190
- val builder =
191
- NotificationCompat
192
- .Builder(context, channelId)
193
- .setSmallIcon(context.applicationInfo.icon)
194
- .setContentTitle(name)
195
- .setContentText("Aborting…")
196
- .setPriority(NotificationCompat.PRIORITY_MAX)
197
- .setCategory(NotificationCompat.CATEGORY_CALL)
198
- .setOngoing(true)
199
- notificationManager.notify(uuid.hashCode(), builder.build())
200
- }
201
-
202
- fun stopRingtone() {
203
- // No-op: Sound is now managed by the Notification Channel life-cycle
204
- }
205
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "1.0.7",
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,10 +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.FOREGROUND_SERVICE_DATA_SYNC', // ADDED THIS
91
- 'android.permission.USE_FULL_SCREEN_INTENT', // ADDED THIS
92
- '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',
93
92
  'android.permission.POST_NOTIFICATIONS',
94
93
  'android.permission.WAKE_LOCK',
95
94
  'android.permission.DISABLE_KEYGUARD',
@@ -108,7 +107,7 @@ function withAndroidConfig(config) {
108
107
 
109
108
  const services = [
110
109
  { name: 'com.rnsnativecall.CallMessagingService', exported: 'false', filter: 'com.google.firebase.MESSAGING_EVENT' },
111
- { name: 'com.rnsnativecall.CallForegroundService', type: 'dataSync' },
110
+ { name: 'com.rnsnativecall.CallForegroundService' },
112
111
  { name: 'com.rnsnativecall.CallUiForegroundService', type: 'phoneCall' },
113
112
  { name: 'com.rnsnativecall.CallHeadlessTask' }
114
113
  ];