rns-nativecall 0.3.4 → 0.3.5

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.
@@ -9,11 +9,12 @@ class CallHeadlessTask : HeadlessJsTaskService() {
9
9
  override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? {
10
10
  val extras = intent.extras
11
11
  return if (extras != null) {
12
+ // INCREASE timeout to 30000 (30s) so it covers your 18s delay
12
13
  HeadlessJsTaskConfig(
13
- "ColdStartCallTask", // This name must match JS registration
14
+ "ColdStartCallTask",
14
15
  Arguments.fromBundle(extras),
15
- 5000, // Timeout for the task
16
- true // Allowed in foreground
16
+ 30000,
17
+ true // Allow in foreground
17
18
  )
18
19
  } else {
19
20
  null
@@ -6,58 +6,108 @@ import android.content.Intent
6
6
  import android.os.Bundle
7
7
  import android.os.Handler
8
8
  import android.os.Looper
9
+ import android.app.NotificationManager
10
+ import androidx.core.app.NotificationCompat
9
11
  import com.google.firebase.messaging.FirebaseMessagingService
10
12
  import com.google.firebase.messaging.RemoteMessage
11
13
  import com.facebook.react.HeadlessJsTaskService
12
14
 
13
15
  class CallMessagingService : FirebaseMessagingService() {
14
16
 
17
+ // This companion object allows the Cancel logic to find the active timer
18
+ companion object {
19
+ private val handler = Handler(Looper.getMainLooper())
20
+ private val pendingNotifications = mutableMapOf<String, Runnable>()
21
+ }
22
+
15
23
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
16
24
  val data = remoteMessage.data
17
25
  val context = applicationContext
18
26
 
27
+ // Extract variables for logic
28
+ val uuid = data["callUuid"] ?: return
29
+ val type = data["type"] ?: ""
30
+
31
+ // CASE 1: CALLER HUNG UP / CANCELLED
32
+ if (type == "CANCEL") {
33
+ // 1. Remove the pending 18s timer for this specific call
34
+ pendingNotifications[uuid]?.let {
35
+ handler.removeCallbacks(it)
36
+ pendingNotifications.remove(uuid)
37
+ }
38
+
39
+ // 2. Stop the ringtone/Pill if it had already started showing
40
+ NativeCallManager.stopRingtone()
41
+ CallHandler.destroyNativeCallUI(uuid) // Ensure native UI is killed
42
+
43
+ // 3. Show standard "Missed Call" notification
44
+ showMissedCallNotification(context, data, uuid)
45
+ return
46
+ }
47
+
48
+ // CASE 2: NEW INCOMING CALL
19
49
  if (isAppInForeground(context)) {
20
- // App is visible: Send event to active JS bridge
50
+ // App is visible: Send event to active JS bridge immediately
21
51
  CallModule.sendEventToJS("onCallReceived", data)
22
52
  } else {
23
- // App is killed/background: Start the Headless JS Task
24
- // This wakes up the JS engine and connects your WebSocket
25
- startHeadlessTask(context, data)
26
-
27
- // Start the 18-second "System Pill" backup timer
28
- val handler = Handler(Looper.getMainLooper())
29
- handler.postDelayed({
30
- // Final safety check: if the user hasn't brought the app to foreground,
31
- // show the native full-screen notification (The Pill)
32
- if (!isAppInForeground(context)) {
33
- NativeCallManager.handleIncomingPush(context, data)
34
- }
35
- }, 18000)
53
+ // 1. Wake the actual App process by launching the Intent
54
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
55
+ launchIntent?.apply {
56
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
57
+ // This is key: it tells the app to boot the JS but stay minimized
58
+ putExtra("background_wake", true)
59
+ }
60
+ context.startActivity(launchIntent)
61
+
62
+ // You SHOULD see "Android Bundled..." now because the Activity is starting
63
+
64
+ val showNotificationRunnable = Runnable {
65
+ if (!isAppInForeground(context)) {
66
+ NativeCallManager.handleIncomingPush(context, data)
36
67
  }
37
68
  }
69
+ handler.postDelayed(showNotificationRunnable, 18000)
70
+ }
71
+ }
38
72
 
39
73
  private fun startHeadlessTask(context: Context, data: Map<String, String>) {
40
74
  val serviceIntent = Intent(context, CallHeadlessTask::class.java)
41
75
  val bundle = Bundle()
42
-
43
- // Pass all FCM data to the JS task
44
76
  data.forEach { (key, value) -> bundle.putString(key, value) }
45
77
  serviceIntent.putExtras(bundle)
46
78
 
47
- // Start the service. This triggers the 'ColdStartCallTask' in index.js
48
79
  context.startService(serviceIntent)
49
80
  HeadlessJsTaskService.acquireWakeLockNow(context)
50
81
  }
51
82
 
83
+ private fun showMissedCallNotification(context: Context, data: Map<String, String>, uuid: String) {
84
+ val name = data["name"] ?: "Unknown"
85
+ val callType = data["callType"] ?: "video"
86
+ val channelId = "missed_calls" // Ensure this channel is created in your MainApplication or CallManager
87
+
88
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
89
+
90
+ // Use ic_launcher as fallback if ic_missed_call isn't found
91
+ var iconResId = context.resources.getIdentifier("ic_missed_call", "drawable", context.packageName)
92
+ if (iconResId == 0) iconResId = android.R.drawable.sym_call_missed
93
+
94
+ val builder = NotificationCompat.Builder(context, channelId)
95
+ .setSmallIcon(iconResId)
96
+ .setContentTitle("Missed $callType call")
97
+ .setContentText("You missed a call from $name")
98
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
99
+ .setAutoCancel(true)
100
+ .setCategory(NotificationCompat.CATEGORY_MISS)
101
+
102
+ notificationManager.notify(uuid.hashCode(), builder.build())
103
+ }
104
+
52
105
  private fun isAppInForeground(context: Context): Boolean {
53
106
  val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
54
107
  val appProcesses = activityManager.runningAppProcesses ?: return false
55
108
  val packageName = context.packageName
56
109
 
57
110
  for (appProcess in appProcesses) {
58
- // IMPORTANCE_FOREGROUND only triggers if a real Activity is on screen.
59
- // Running a Headless Task counts as IMPORTANCE_VISIBLE or lower,
60
- // so this check remains accurate.
61
111
  if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
62
112
  appProcess.processName == packageName) {
63
113
  return true
@@ -28,7 +28,6 @@ object NativeCallManager {
28
28
  } else {
29
29
  PendingIntent.FLAG_UPDATE_CURRENT
30
30
  }
31
-
32
31
  // 1. IMPROVED INTENT: Direct to AcceptCallActivity
33
32
  // This is better than a dummy intent because it allows the OS to
34
33
  // launch your "Accept" logic immediately if the phone is locked.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
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",
@@ -5,10 +5,15 @@ function withMainActivityDataFix(config) {
5
5
  return withMainActivity(config, (config) => {
6
6
  let contents = config.modResults.contents;
7
7
 
8
+ // 1. Ensure necessary Imports are present
8
9
  if (!contents.includes('import android.content.Intent')) {
9
10
  contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
10
11
  }
12
+ if (!contents.includes('import android.os.Bundle')) {
13
+ contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.os.Bundle`);
14
+ }
11
15
 
16
+ // 2. Define the code blocks
12
17
  const onNewIntentCode = `
13
18
  override fun onNewIntent(intent: Intent) {
14
19
  super.onNewIntent(intent)
@@ -16,11 +21,28 @@ function withMainActivityDataFix(config) {
16
21
  }
17
22
  `;
18
23
 
24
+ const onCreateCode = `
25
+ override fun onCreate(savedInstanceState: Bundle?) {
26
+ super.onCreate(savedInstanceState)
27
+ // If woken up silently, move to back to prevent UI flicker
28
+ if (intent.getBooleanExtra("background_wake", false)) {
29
+ moveTaskToBack(true)
30
+ }
31
+ }
32
+ `;
33
+
34
+ // 3. Insert the codes before the last closing brace of the class
19
35
  if (!contents.includes('override fun onNewIntent')) {
20
36
  const lastBraceIndex = contents.lastIndexOf('}');
21
37
  contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
22
38
  }
23
39
 
40
+ if (!contents.includes('override fun onCreate')) {
41
+ // Re-calculate lastBraceIndex because contents string has changed
42
+ const lastBraceIndex = contents.lastIndexOf('}');
43
+ contents = contents.slice(0, lastBraceIndex) + onCreateCode + contents.slice(lastBraceIndex);
44
+ }
45
+
24
46
  config.modResults.contents = contents;
25
47
  return config;
26
48
  });
@@ -104,6 +126,8 @@ function withAndroidConfig(config) {
104
126
  });
105
127
  }
106
128
 
129
+
130
+
107
131
  return config;
108
132
  });
109
133
  }