rns-nativecall 0.4.6 → 0.4.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.
@@ -15,8 +15,6 @@ import com.google.firebase.messaging.FirebaseMessagingService
15
15
  import com.google.firebase.messaging.RemoteMessage
16
16
  import com.facebook.react.HeadlessJsTaskService
17
17
 
18
- import android.os.PowerManager
19
-
20
18
  class CallMessagingService : FirebaseMessagingService() {
21
19
 
22
20
  companion object {
@@ -41,36 +39,33 @@ class CallMessagingService : FirebaseMessagingService() {
41
39
  }
42
40
 
43
41
  if (isAppInForeground(context)) {
42
+ // When in foreground, we just tell JS to show the in-app UI
44
43
  CallModule.sendEventToJS("onCallReceived", data)
45
44
  } else {
46
- // 1. Prepare Intent
47
- val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
48
- putExtras(Bundle().apply {
49
- data.forEach { (k, v) -> putString(k, v) }
50
- })
51
- }
45
+ // 1. Show the Notification IMMEDIATELY
46
+ // Do not wait for HeadlessJS. Android requires immediate feedback for background starts.
47
+ NativeCallManager.handleIncomingPush(context, data)
52
48
 
53
- // 2. Start Service correctly based on SDK
54
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
55
- context.startForegroundService(headlessIntent)
56
- } else {
57
- context.startService(headlessIntent)
58
- }
59
-
60
- // 3. WakeLock
49
+ // 2. Start HeadlessJS Task to sync Javascript state (WebRTC initialization, etc.)
61
50
  try {
51
+ val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
52
+ putExtras(Bundle().apply {
53
+ data.forEach { (k, v) -> putString(k, v) }
54
+ })
55
+ }
56
+
57
+ // Use a standard startService. If CallHeadlessTask is a HeadlessJsTaskService,
58
+ // it handles its own foreground/background lifecycle.
59
+ context.startService(headlessIntent)
62
60
  HeadlessJsTaskService.acquireWakeLockNow(context)
63
- } catch (e: Exception) { e.printStackTrace() }
64
-
65
- val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
66
- val wakeLock = powerManager.newWakeLock(
67
- PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE,
68
- "NativeCall:IncomingCallWakeLock"
69
- )
70
- wakeLock.acquire(3000) // Wake screen for 3 seconds to show notification
61
+ } catch (e: Exception) {
62
+ e.printStackTrace()
63
+ }
71
64
 
72
- // 4. Backup Timer
65
+ // 3. Backup Safety: Ensure if JS fails, the notification is eventually handled
73
66
  val showNotificationRunnable = Runnable {
67
+ // If the notification was somehow canceled or not showing, re-trigger
68
+ // (Usually not needed if handleIncomingPush is called above, but kept for your logic)
74
69
  if (!isAppInForeground(context)) {
75
70
  NativeCallManager.handleIncomingPush(context, data)
76
71
  }
@@ -1,130 +1,130 @@
1
1
  package com.rnsnativecall
2
2
 
3
- import android.app.Notification
4
3
  import android.app.NotificationChannel
5
4
  import android.app.NotificationManager
6
5
  import android.app.PendingIntent
7
6
  import android.content.Context
8
- import android.content.Intent
9
- import android.graphics.Color
10
- import android.media.AudioAttributes
11
- import android.media.RingtoneManager
7
+ import android.content.Intent // CRITICAL IMPORT
12
8
  import android.os.Build
9
+ import android.os.Bundle // CRITICAL IMPORT
13
10
  import androidx.core.app.NotificationCompat
11
+ import android.media.Ringtone
12
+ import android.media.RingtoneManager
13
+ import android.graphics.Color
14
14
  import androidx.core.app.Person
15
- import android.graphics.drawable.Icon
15
+ import androidx.core.app.NotificationCompat.CallStyle
16
16
 
17
17
  object NativeCallManager {
18
18
 
19
+ private var ringtone: Ringtone? = null
19
20
  const val CALL_CHANNEL_ID = "CALL_CHANNEL_ID"
20
21
 
21
22
  fun handleIncomingPush(context: Context, data: Map<String, String>) {
22
23
  val uuid = data["callUuid"] ?: return
23
- val name = data["name"] ?: "Unknown Caller"
24
- val handle = data["handle"] ?: ""
25
24
  stopRingtone()
26
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
25
+
26
+ val name = data["name"] ?: "Someone"
27
+ val callType = data["callType"] ?: "audio"
27
28
 
28
- // 1. Create Channel with High Importance (Required for Heads Up)
29
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
30
- val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
31
- val audioAttributes = AudioAttributes.Builder()
32
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
33
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
34
- .build()
35
-
36
- val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
37
- setSound(ringtoneUri, audioAttributes)
38
- enableVibration(true)
39
- lockscreenVisibility = Notification.VISIBILITY_PUBLIC
40
- setBypassDnd(true) // Optional: Break through Do Not Disturb
41
- }
42
- notificationManager.createNotificationChannel(channel)
29
+ val pendingFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
30
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
31
+ } else {
32
+ PendingIntent.FLAG_UPDATE_CURRENT
43
33
  }
44
34
 
45
- // 2. Prepare Intents
46
- val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
47
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
48
- else PendingIntent.FLAG_UPDATE_CURRENT
49
-
50
- // A. Full Screen Intent (Triggers the Heads-up logic)
51
- // We point this to AcceptCallActivity, which has showWhenLocked="false"
52
- // Result: System tries to launch, hits the lock restriction, and falls back to showing the Notification Pill.
35
+ // 1. Full Screen / UI Intent
53
36
  val intentToActivity = Intent(context, AcceptCallActivity::class.java).apply {
54
- action = "ACTION_SHOW_INCOMING_$uuid"
55
- data.forEach { (k, v) -> putExtra(k, v) }
37
+ action = "ACTION_SHOW_UI_$uuid"
38
+ data.forEach { (key, value) -> putExtra(key, value) }
39
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
56
40
  }
57
- val fullScreenPendingIntent = PendingIntent.getActivity(context, uuid.hashCode(), intentToActivity, flags)
41
+ val fullScreenPendingIntent = PendingIntent.getActivity(
42
+ context, uuid.hashCode(), intentToActivity, pendingFlags
43
+ )
58
44
 
59
- // B. Answer Intent
60
- // We point this to MainActivity (or a BroadcastReceiver that launches MainActivity)
61
- // MainActivity HAS showWhenLocked="true", so it WILL open over the lock screen when tapped.
62
- val packageName = context.packageName
63
- val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) ?: return
64
- launchIntent.apply {
65
- action = "ACTION_ANSWER_CALL"
66
- putExtra("callUuid", uuid)
67
- data.forEach { (k, v) -> putExtra(k, v) }
68
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
45
+ // 2. Reject Intent
46
+ val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
47
+ action = "ACTION_REJECT_$uuid"
48
+ putExtra("EXTRA_CALL_UUID", uuid)
49
+ data.forEach { (key, value) -> putExtra(key, value) }
69
50
  }
70
- val answerPendingIntent = PendingIntent.getActivity(context, uuid.hashCode() + 1, launchIntent, flags)
51
+ val rejectPendingIntent = PendingIntent.getBroadcast(
52
+ context, uuid.hashCode() + 1, rejectIntent, pendingFlags
53
+ )
71
54
 
72
- // C. Decline Intent
73
- val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
74
- action = "ACTION_REJECT_CALL"
75
- putExtra("callUuid", uuid)
55
+ // 3. Answer Intent (Using your CallActionReceiver)
56
+ val answerIntent = Intent(context, CallActionReceiver::class.java).apply {
57
+ action = "ACTION_ACCEPT_$uuid" // Changed to match your Receiver logic
58
+ putExtra("EXTRA_CALL_UUID", uuid)
59
+ data.forEach { (key, value) -> putExtra(key, value) }
76
60
  }
77
- val rejectPendingIntent = PendingIntent.getBroadcast(context, uuid.hashCode() + 2, rejectIntent, flags)
61
+ val answerPendingIntent = PendingIntent.getBroadcast(
62
+ context, uuid.hashCode() + 2, answerIntent, pendingFlags
63
+ )
78
64
 
79
- // 3. Build Notification using CallStyle (Android 12+) or Custom (Older)
80
- val person = Person.Builder()
65
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
66
+
67
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
68
+ val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Call", NotificationManager.IMPORTANCE_HIGH).apply {
69
+ setBypassDnd(true)
70
+ lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
71
+ enableVibration(true)
72
+ setSound(null, null)
73
+ }
74
+ notificationManager.createNotificationChannel(channel)
75
+ }
76
+
77
+ val caller = Person.Builder()
81
78
  .setName(name)
82
- .setKey(uuid)
79
+ .setImportant(true)
83
80
  .build()
84
81
 
85
82
  val builder = NotificationCompat.Builder(context, CALL_CHANNEL_ID)
86
83
  .setSmallIcon(context.applicationInfo.icon)
84
+ .setContentTitle("Incoming $callType call")
85
+ .setContentText(name)
87
86
  .setPriority(NotificationCompat.PRIORITY_MAX)
88
87
  .setCategory(NotificationCompat.CATEGORY_CALL)
88
+ .setOngoing(true)
89
89
  .setAutoCancel(false)
90
- .setOngoing(true)
91
- .setColor(Color.parseColor("#00C853"))
92
- // Crucial: Set the full screen intent, but let the Manifest block the auto-launch
93
- .setFullScreenIntent(fullScreenPendingIntent, true)
94
-
95
- // Apply Native Call Style (The "Pill")
96
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
97
- builder.setStyle(
98
- NotificationCompat.CallStyle.forIncomingCall(person, rejectPendingIntent, answerPendingIntent)
90
+ .setFullScreenIntent(fullScreenPendingIntent, true)
91
+ .setColor(Color.parseColor("#28a745"))
92
+ .setColorized(true)
93
+ .setStyle(
94
+ NotificationCompat.CallStyle.forIncomingCall(
95
+ caller,
96
+ rejectPendingIntent,
97
+ answerPendingIntent
98
+ )
99
99
  )
100
- } else {
101
- // Fallback for Android 10/11
102
- builder.setContentTitle("Incoming Call")
103
- .setContentText(name)
104
- .addAction(android.R.drawable.ic_menu_call, "Answer", answerPendingIntent)
105
- .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Decline", rejectPendingIntent)
106
- }
107
-
100
+
108
101
  notificationManager.notify(uuid.hashCode(), builder.build())
102
+
103
+ try {
104
+ val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
105
+ ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
106
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ringtone?.isLooping = true
107
+ ringtone?.play()
108
+ } catch (e: Exception) { e.printStackTrace() }
109
+ }
110
+
111
+ fun stopRingtone() {
112
+ try {
113
+ ringtone?.let {
114
+ if (it.isPlaying) it.stop()
115
+ }
116
+ ringtone = null
117
+ } catch (e: Exception) {
118
+ e.printStackTrace()
119
+ ringtone = null
120
+ }
109
121
  }
110
- fun stopRingtone() {
111
-     try {
112
-         // Use the safe call operator ?. to only run if ringtone isn't null
113
-         ringtone?.let {
114
-             if (it.isPlaying) {
115
-                 it.stop()
116
-             }
117
-         }
118
-         // Always null it out after stopping
119
-         ringtone = null
120
-     } catch (e: Exception) {
121
-         // Prevent the app from crashing if the system Ringtone service is acting up
122
-         e.printStackTrace()
123
-         ringtone = null
124
-     }
122
+
125
123
  fun dismissIncomingCall(context: Context, uuid: String?) {
126
- stopRingtone()
127
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
128
- if (uuid != null) notificationManager.cancel(uuid.hashCode())
124
+ stopRingtone()
125
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
126
+ if (uuid != null) {
127
+ notificationManager.cancel(uuid.hashCode())
128
+ }
129
129
  }
130
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "RNS nativecall component with native Android/iOS for handling native call ui, when app is not open or open.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -9,7 +9,8 @@ function withMainActivityDataFix(config) {
9
9
  'import android.view.WindowManager',
10
10
  'import android.os.Build',
11
11
  'import android.os.Bundle',
12
- 'import android.content.Intent'
12
+ 'import android.content.Intent',
13
+ 'import android.content.Context' // Added: Required for KEYGUARD_SERVICE
13
14
  ];
14
15
 
15
16
  imports.forEach(imp => {
@@ -18,14 +19,6 @@ function withMainActivityDataFix(config) {
18
19
  }
19
20
  });
20
21
 
21
- imports.forEach(imp => {
22
- if (!contents.includes(imp)) {
23
- contents = contents.replace(/package .*/, (match) => `${match}\n${imp}`);
24
- }
25
- });
26
-
27
- // FIXED: Corrected Kotlin bitwise OR syntax and logic check
28
- // We check for "navigatingToCall" so this only runs when Answer is tapped
29
22
  const wakeLogic = `super.onCreate(savedInstanceState)
30
23
  if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
31
24
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@@ -35,13 +28,10 @@ function withMainActivityDataFix(config) {
35
28
  window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
36
29
  }
37
30
 
38
- // Remove Keyguard so we jump straight to app (optional but recommended)
39
31
  val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as? android.app.KeyguardManager
40
32
  keyguardManager?.requestDismissKeyguard(this, null)
41
33
  }`;
42
34
 
43
-
44
-
45
35
  if (!contents.includes('setShowWhenLocked')) {
46
36
  contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogic);
47
37
  }
@@ -67,7 +57,7 @@ function withAndroidConfig(config) {
67
57
  const androidManifest = config.modResults.manifest;
68
58
  const mainApplication = androidManifest.application[0];
69
59
 
70
- // 2. Permissions
60
+ // Permissions
71
61
  const permissions = [
72
62
  'android.permission.USE_FULL_SCREEN_INTENT',
73
63
  'android.permission.VIBRATE',
@@ -88,7 +78,7 @@ function withAndroidConfig(config) {
88
78
  }
89
79
  });
90
80
 
91
- // 3. Activity/Service/Receiver registration (Cleaned up duplicates)
81
+ // Activity Registration: AcceptCallActivity
92
82
  mainApplication.activity = mainApplication.activity || [];
93
83
  if (!mainApplication.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
94
84
  mainApplication.activity.push({
@@ -96,8 +86,19 @@ function withAndroidConfig(config) {
96
86
  'android:name': 'com.rnsnativecall.AcceptCallActivity',
97
87
  'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
98
88
  'android:exported': 'false',
99
- 'android:showWhenLocked': 'false',
100
- 'android:turnScreenOn': 'false'
89
+ 'android:showWhenLocked': 'true', // Changed to true for calling
90
+ 'android:turnScreenOn': 'true' // Changed to true for calling
91
+ }
92
+ });
93
+ }
94
+
95
+ // Receiver Registration: CallActionReceiver
96
+ mainApplication.receiver = mainApplication.receiver || [];
97
+ if (!mainApplication.receiver.some(r => r.$['android:name'] === 'com.rnsnativecall.CallActionReceiver')) {
98
+ mainApplication.receiver.push({
99
+ $: {
100
+ 'android:name': 'com.rnsnativecall.CallActionReceiver',
101
+ 'android:exported': 'false'
101
102
  }
102
103
  });
103
104
  }
@@ -112,7 +113,7 @@ function withAndroidConfig(config) {
112
113
  });
113
114
  }
114
115
 
115
- // Headless Task Service (Fixed for Android 14)
116
+ // Headless Task Service (Android 14)
116
117
  const headlessName = 'com.rnsnativecall.CallHeadlessTask';
117
118
  const headlessIndex = mainApplication.service.findIndex(s => s.$['android:name'] === headlessName);
118
119
  const headlessEntry = {