rns-nativecall 0.4.4 → 0.4.6

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.
@@ -25,8 +25,7 @@ class CallHeadlessTask : HeadlessJsTaskService() {
25
25
  }
26
26
 
27
27
  val notification: Notification = NotificationCompat.Builder(this, channelId)
28
- .setContentTitle("")
29
- // .setSmallIcon(android.R.drawable.sym_call_incoming)
28
+ .setContentTitle("incoming...")
30
29
  .setPriority(NotificationCompat.PRIORITY_LOW)
31
30
  .build()
32
31
 
@@ -15,6 +15,8 @@ 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
+
18
20
  class CallMessagingService : FirebaseMessagingService() {
19
21
 
20
22
  companion object {
@@ -60,6 +62,13 @@ class CallMessagingService : FirebaseMessagingService() {
60
62
  HeadlessJsTaskService.acquireWakeLockNow(context)
61
63
  } catch (e: Exception) { e.printStackTrace() }
62
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
71
+
63
72
  // 4. Backup Timer
64
73
  val showNotificationRunnable = Runnable {
65
74
  if (!isAppInForeground(context)) {
@@ -1,119 +1,130 @@
1
1
  package com.rnsnativecall
2
2
 
3
+ import android.app.Notification
3
4
  import android.app.NotificationChannel
4
5
  import android.app.NotificationManager
5
6
  import android.app.PendingIntent
6
7
  import android.content.Context
7
8
  import android.content.Intent
9
+ import android.graphics.Color
10
+ import android.media.AudioAttributes
11
+ import android.media.RingtoneManager
8
12
  import android.os.Build
9
13
  import androidx.core.app.NotificationCompat
10
- import android.media.Ringtone
11
- import android.media.RingtoneManager
12
- import android.graphics.Color
13
-
14
14
  import androidx.core.app.Person
15
- import androidx.core.app.NotificationCompat.CallStyle
15
+ import android.graphics.drawable.Icon
16
16
 
17
17
  object NativeCallManager {
18
18
 
19
- private var ringtone: Ringtone? = null
20
19
  const val CALL_CHANNEL_ID = "CALL_CHANNEL_ID"
21
20
 
22
21
  fun handleIncomingPush(context: Context, data: Map<String, String>) {
23
22
  val uuid = data["callUuid"] ?: return
23
+ val name = data["name"] ?: "Unknown Caller"
24
+ val handle = data["handle"] ?: ""
24
25
  stopRingtone()
25
-
26
- val name = data["name"] ?: "Someone"
27
- val callType = data["callType"] ?: "audio"
26
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
28
27
 
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
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)
33
43
  }
34
44
 
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
53
  val intentToActivity = Intent(context, AcceptCallActivity::class.java).apply {
36
- action = "ACTION_SHOW_UI_$uuid"
37
- data.forEach { (key, value) -> putExtra(key, value) }
38
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
54
+ action = "ACTION_SHOW_INCOMING_$uuid"
55
+ data.forEach { (k, v) -> putExtra(k, v) }
39
56
  }
57
+ val fullScreenPendingIntent = PendingIntent.getActivity(context, uuid.hashCode(), intentToActivity, flags)
58
+
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)
69
+ }
70
+ val answerPendingIntent = PendingIntent.getActivity(context, uuid.hashCode() + 1, launchIntent, flags)
40
71
 
41
- val fullScreenPendingIntent = PendingIntent.getActivity(context, uuid.hashCode(), intentToActivity, pendingFlags)
42
-
72
+ // C. Decline Intent
43
73
  val rejectIntent = Intent(context, CallActionReceiver::class.java).apply {
44
- action = "ACTION_REJECT_$uuid"
45
- putExtra("EXTRA_CALL_UUID", uuid)
46
- data.forEach { (key, value) -> putExtra(key, value) }
74
+ action = "ACTION_REJECT_CALL"
75
+ putExtra("callUuid", uuid)
47
76
  }
77
+ val rejectPendingIntent = PendingIntent.getBroadcast(context, uuid.hashCode() + 2, rejectIntent, flags)
48
78
 
49
-
50
- val rejectPendingIntent = PendingIntent.getBroadcast(context, uuid.hashCode() + 1, rejectIntent, pendingFlags)
51
-
52
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
53
-
54
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
55
- val channel = NotificationChannel(CALL_CHANNEL_ID, "Incoming Call", NotificationManager.IMPORTANCE_HIGH).apply {
56
- setBypassDnd(true)
57
- lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
58
- enableVibration(false) //can be removed
59
- setSound(null, null)
60
- }
61
- notificationManager.createNotificationChannel(channel)
62
- }
79
+ // 3. Build Notification using CallStyle (Android 12+) or Custom (Older)
80
+ val person = Person.Builder()
81
+ .setName(name)
82
+ .setKey(uuid)
83
+ .build()
63
84
 
64
85
  val builder = NotificationCompat.Builder(context, CALL_CHANNEL_ID)
65
86
  .setSmallIcon(context.applicationInfo.icon)
66
- .setContentTitle("Incoming $callType call")
67
- .setContentText(name)
68
-
69
- .setPriority(NotificationCompat.PRIORITY_HIGH) //old was PRIORITY_MAX
87
+ .setPriority(NotificationCompat.PRIORITY_MAX)
70
88
  .setCategory(NotificationCompat.CATEGORY_CALL)
71
- .setOngoing(true)
72
89
  .setAutoCancel(false)
73
-
74
- .setFullScreenIntent(fullScreenPendingIntent, false) //can be true
75
- .addAction(0, "Answer", fullScreenPendingIntent)
76
- .addAction(0, "Decline", rejectPendingIntent)
77
- .setColor(Color.parseColor("#28a745")) // Green color for the "Pill"
78
- .setColorized(true)
79
-
80
- notificationManager.notify(uuid.hashCode(), builder.build())
81
-
82
- try {
83
- val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
84
- ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
85
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ringtone?.isLooping = true
86
- ringtone?.play()
87
- } catch (e: Exception) { e.printStackTrace() }
88
- }
89
-
90
- fun stopRingtone() {
91
- try {
92
- // Use the safe call operator ?. to only run if ringtone isn't null
93
- ringtone?.let {
94
- if (it.isPlaying) {
95
- it.stop()
96
- }
97
- }
98
- // Always null it out after stopping
99
- ringtone = null
100
- } catch (e: Exception) {
101
- // Prevent the app from crashing if the system Ringtone service is acting up
102
- e.printStackTrace()
103
- ringtone = null
104
- }
105
- }
106
-
107
- fun dismissIncomingCall(context: Context, uuid: String?) {
108
- stopRingtone()
109
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
110
-
111
- if (uuid != null) {
112
- notificationManager.cancel(uuid.hashCode())
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)
99
+ )
113
100
  } else {
114
- // Fallback: If for some reason uuid is null, cancel everything
115
- // (Optional: only if you want a safety net)
116
- // notificationManager.cancelAll()
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)
117
106
  }
107
+
108
+ notificationManager.notify(uuid.hashCode(), builder.build())
109
+ }
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
+     }
125
+ 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())
118
129
  }
119
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
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",
@@ -18,7 +18,14 @@ function withMainActivityDataFix(config) {
18
18
  }
19
19
  });
20
20
 
21
+ imports.forEach(imp => {
22
+ if (!contents.includes(imp)) {
23
+ contents = contents.replace(/package .*/, (match) => `${match}\n${imp}`);
24
+ }
25
+ });
26
+
21
27
  // FIXED: Corrected Kotlin bitwise OR syntax and logic check
28
+ // We check for "navigatingToCall" so this only runs when Answer is tapped
22
29
  const wakeLogic = `super.onCreate(savedInstanceState)
23
30
  if (intent?.getBooleanExtra("navigatingToCall", false) == true) {
24
31
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
@@ -27,8 +34,14 @@ function withMainActivityDataFix(config) {
27
34
  } else {
28
35
  window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
29
36
  }
37
+
38
+ // Remove Keyguard so we jump straight to app (optional but recommended)
39
+ val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as? android.app.KeyguardManager
40
+ keyguardManager?.requestDismissKeyguard(this, null)
30
41
  }`;
31
42
 
43
+
44
+
32
45
  if (!contents.includes('setShowWhenLocked')) {
33
46
  contents = contents.replace(/super\.onCreate\(.*\)/, wakeLogic);
34
47
  }
@@ -54,13 +67,6 @@ function withAndroidConfig(config) {
54
67
  const androidManifest = config.modResults.manifest;
55
68
  const mainApplication = androidManifest.application[0];
56
69
 
57
- // 1. MainActivity flags
58
- const mainActivity = mainApplication.activity.find(a => a.$["android:name"] === ".MainActivity");
59
- if (mainActivity) {
60
- mainActivity.$["android:showWhenLocked"] = "true";
61
- mainActivity.$["android:turnScreenOn"] = "true";
62
- }
63
-
64
70
  // 2. Permissions
65
71
  const permissions = [
66
72
  'android.permission.USE_FULL_SCREEN_INTENT',
@@ -90,8 +96,8 @@ function withAndroidConfig(config) {
90
96
  'android:name': 'com.rnsnativecall.AcceptCallActivity',
91
97
  'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
92
98
  'android:exported': 'false',
93
- 'android:showWhenLocked': 'true',
94
- 'android:turnScreenOn': 'true'
99
+ 'android:showWhenLocked': 'false',
100
+ 'android:turnScreenOn': 'false'
95
101
  }
96
102
  });
97
103
  }