rns-nativecall 0.6.6 → 0.6.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.
@@ -0,0 +1,104 @@
1
+ package com.rnsnativecall
2
+
3
+ import android.app.*
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.content.pm.ServiceInfo
7
+ import android.os.Build
8
+ import android.os.Bundle
9
+ import android.os.IBinder
10
+ import androidx.core.app.NotificationCompat
11
+ import com.facebook.react.HeadlessJsTaskService
12
+
13
+ import android.os.Handler // Added
14
+ import android.os.Looper // Added
15
+
16
+ class CallForegroundService : Service() {
17
+
18
+ companion object {
19
+ private const val NOTIFICATION_ID = 101
20
+ private const val CHANNEL_ID = "incoming_call_service"
21
+
22
+ fun stop(context: Context) {
23
+ val intent = Intent(context, CallForegroundService::class.java)
24
+ context.stopService(intent)
25
+ }
26
+ }
27
+
28
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
29
+ val data = intent?.extras
30
+ val name = data?.getString("name") ?: "Someone"
31
+
32
+ createNotificationChannel()
33
+
34
+ val notification = NotificationCompat.Builder(this, CHANNEL_ID)
35
+ .setContentTitle(name)
36
+ .setContentText("Connecting...")
37
+ .setSmallIcon(applicationInfo.icon)
38
+ .setCategory(NotificationCompat.CATEGORY_CALL)
39
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
40
+ .setOngoing(true)
41
+ .build()
42
+
43
+ // --- ANDROID 14 FIX START ---
44
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
45
+ startForeground(
46
+ NOTIFICATION_ID,
47
+ notification,
48
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL
49
+ )
50
+ } else {
51
+ startForeground(NOTIFICATION_ID, notification)
52
+ }
53
+ // --- ANDROID 14 FIX END ---
54
+
55
+ // Launch the Headless Task
56
+ val headlessIntent = Intent(this, CallHeadlessTask::class.java).apply {
57
+ // Safe bundle copy
58
+ val bundle = Bundle()
59
+ data?.let { b ->
60
+ for (key in b.keySet()) {
61
+ bundle.putString(key, b.get(key)?.toString())
62
+ }
63
+ }
64
+ putExtras(bundle)
65
+ }
66
+
67
+ try {
68
+ this.startService(headlessIntent)
69
+ HeadlessJsTaskService.acquireWakeLockNow(this)
70
+ } catch (e: Exception) {
71
+ e.printStackTrace()
72
+ }
73
+
74
+
75
+ // Automatically stop this service if JS doesn't stop it within 30 seconds
76
+ Handler(Looper.getMainLooper()).postDelayed({
77
+ try {
78
+ stopSelf()
79
+ } catch (e: Exception) {
80
+ // Service might already be stopped
81
+ }
82
+ }, 30000)
83
+ return START_NOT_STICKY
84
+ }
85
+
86
+ private fun createNotificationChannel() {
87
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
88
+ val channel = NotificationChannel(
89
+ CHANNEL_ID,
90
+ "Call Service",
91
+ NotificationManager.IMPORTANCE_HIGH
92
+ ).apply {
93
+ description = "Handles incoming call connection state"
94
+ // Optional: makes the "Connecting" notification silent
95
+ // so it doesn't double-beep with the actual ringtone
96
+ setSound(null, null)
97
+ }
98
+ val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
99
+ manager.createNotificationChannel(channel)
100
+ }
101
+ }
102
+
103
+ override fun onBind(intent: Intent?): IBinder? = null
104
+ }
@@ -49,6 +49,9 @@ if (!CallState.setCurrent(uuid)) {
49
49
  return
50
50
  }
51
51
 
52
+
53
+
54
+
52
55
  if (isAppInForeground(context)) {
53
56
  // Foreground → send event directly
54
57
  CallModule.sendEventToJS("onCallReceived", data)
@@ -61,16 +64,29 @@ if (!CallState.setCurrent(uuid)) {
61
64
  // }
62
65
  // ContextCompat.startForegroundService(context, serviceIntent)
63
66
 
64
- val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
65
- putExtras(Bundle().apply { data.forEach { (k, v) -> putString(k, v) } })
66
- }
67
- try {
68
- context.startService(headlessIntent)
69
- HeadlessJsTaskService.acquireWakeLockNow(context)
70
- } catch (e: Exception) {
71
- e.printStackTrace()
72
- }
67
+ // val headlessIntent = Intent(context, CallHeadlessTask::class.java).apply {
68
+ // putExtras(Bundle().apply { data.forEach { (k, v) -> putString(k, v) } })
69
+ // }
70
+ // try {
71
+ // context.startService(headlessIntent)
72
+ // HeadlessJsTaskService.acquireWakeLockNow(context)
73
+ // } catch (e: Exception) {
74
+ // e.printStackTrace()
75
+ // }
76
+
73
77
 
78
+ // Background → start foreground service (which in turn starts headless)
79
+ val serviceIntent = Intent(context, CallForegroundService::class.java).apply {
80
+ putExtras(Bundle().apply {
81
+ data.forEach { (k, v) -> putString(k, v) }
82
+ })
83
+ }
84
+
85
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
86
+ context.startForegroundService(serviceIntent)
87
+ } else {
88
+ context.startService(serviceIntent)
89
+ }
74
90
 
75
91
  }
76
92
  }
@@ -4,6 +4,7 @@ import android.app.NotificationManager
4
4
  import android.content.Context
5
5
  import com.facebook.react.bridge.*
6
6
  import com.facebook.react.modules.core.DeviceEventManagerModule
7
+ import android.content.Intent
7
8
 
8
9
  class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
9
10
 
@@ -66,6 +67,18 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
66
67
  promise.resolve(map)
67
68
  }
68
69
 
70
+ @ReactMethod
71
+ fun stopForegroundService(promise: Promise) {
72
+ try {
73
+ // Using the current react context to stop the service
74
+ val intent = Intent(reactApplicationContext, CallForegroundService::class.java)
75
+ reactApplicationContext.stopService(intent)
76
+ promise.resolve(true)
77
+ } catch (e: Exception) {
78
+ promise.reject("SERVICE_STOP_ERROR", e.message)
79
+ }
80
+ }
81
+
69
82
  /**
70
83
  * General Status Check:
71
84
  * Useful for checking if the UI is still relevant.
package/index.d.ts CHANGED
@@ -25,6 +25,8 @@ export interface CallHandlerType {
25
25
 
26
26
  destroyNativeCallUI(uuid: string): void;
27
27
 
28
+ stopForegroundService(): Promise<void>;
29
+
28
30
  getInitialCallData(): Promise<any | null>;
29
31
 
30
32
  subscribe(
package/index.js CHANGED
@@ -37,6 +37,7 @@ export const CallHandler = {
37
37
  if (onAction) {
38
38
  await onAction(data, 'INCOMING_CALL');
39
39
  }
40
+
40
41
  } catch (error) {
41
42
  console.error('[RNSNativeCall] Headless Task Error:', error);
42
43
  }
@@ -48,6 +49,10 @@ export const CallHandler = {
48
49
  return await CallModule.displayIncomingCall(uuid.toLowerCase().trim(), name, callType);
49
50
  },
50
51
 
52
+ stopForegroundService: async () => {
53
+ await CallModule.stopForegroundService(true)
54
+ },
55
+
51
56
  destroyNativeCallUI: (uuid) => {
52
57
  if (CallModule?.endNativeCall) {
53
58
  CallModule.endNativeCall(uuid.toLowerCase().trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.6.6",
3
+ "version": "0.6.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",
@@ -5,6 +5,11 @@ function withMainActivityDataFix(config) {
5
5
  return withMainActivity(config, (config) => {
6
6
  let contents = config.modResults.contents;
7
7
 
8
+ // Ensure Intent import for Kotlin
9
+ if (!contents.includes('import android.content.Intent')) {
10
+ contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
11
+ }
12
+
8
13
  const onNewIntentCode = `
9
14
  override fun onNewIntent(intent: Intent) {
10
15
  super.onNewIntent(intent)
@@ -35,11 +40,14 @@ function withMainActivityDataFix(config) {
35
40
  return config;
36
41
  });
37
42
  }
43
+
38
44
  /** 2. ANDROID MANIFEST CONFIG **/
39
45
  function withAndroidConfig(config) {
40
46
  return withAndroidManifest(config, (config) => {
41
47
  const manifest = config.modResults;
42
48
  const application = manifest.manifest.application[0];
49
+
50
+ // Comprehensive list for VoIP & Foreground Services
43
51
  const permissions = [
44
52
  'android.permission.USE_FULL_SCREEN_INTENT',
45
53
  'android.permission.VIBRATE',
@@ -48,15 +56,19 @@ function withAndroidConfig(config) {
48
56
  'android.permission.POST_NOTIFICATIONS',
49
57
  'android.permission.SYSTEM_ALERT_WINDOW',
50
58
  'android.permission.WAKE_LOCK',
51
- 'android.permission.DISABLE_KEYGUARD'
59
+ 'android.permission.DISABLE_KEYGUARD',
60
+ 'android.permission.MANAGE_OWN_CALLS'
52
61
  ];
62
+
53
63
  manifest.manifest['uses-permission'] = manifest.manifest['uses-permission'] || [];
54
64
  permissions.forEach((perm) => {
55
65
  if (!manifest.manifest['uses-permission'].some((p) => p.$['android:name'] === perm)) {
56
66
  manifest.manifest['uses-permission'].push({ $: { 'android:name': perm } });
57
67
  }
58
68
  });
69
+
59
70
  application.activity = application.activity || [];
71
+ // AcceptCallActivity registration
60
72
  if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
61
73
  application.activity.push({
62
74
  $: {
@@ -71,21 +83,10 @@ function withAndroidConfig(config) {
71
83
  }
72
84
  });
73
85
  }
74
- if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.UnlockPromptActivity')) {
75
- application.activity.push({
76
- $: {
77
- 'android:name': 'com.rnsnativecall.UnlockPromptActivity',
78
- 'android:theme': '@android:style/Theme.Translucent.NoTitleBar',
79
- 'android:excludeFromRecents': 'true',
80
- 'android:noHistory': 'true',
81
- 'android:exported': 'false',
82
- 'android:launchMode': 'singleInstance',
83
- 'android:showWhenLocked': 'true',
84
- 'android:turnScreenOn': 'true'
85
- }
86
- });
87
- }
86
+
88
87
  application.service = application.service || [];
88
+
89
+ // 1. Firebase Messaging Service
89
90
  const firebaseServiceName = 'com.rnsnativecall.CallMessagingService';
90
91
  if (!application.service.some(s => s.$['android:name'] === firebaseServiceName)) {
91
92
  application.service.push({
@@ -93,12 +94,27 @@ function withAndroidConfig(config) {
93
94
  'intent-filter': [{ action: [{ $: { 'android:name': 'com.google.firebase.MESSAGING_EVENT' } }] }]
94
95
  });
95
96
  }
97
+
98
+ // 2. Headless Task Service
96
99
  const headlessServiceName = 'com.rnsnativecall.CallHeadlessTask';
97
100
  if (!application.service.some(s => s.$['android:name'] === headlessServiceName)) {
98
101
  application.service.push({
99
102
  $: { 'android:name': headlessServiceName, 'android:exported': 'false' }
100
103
  });
101
104
  }
105
+
106
+ // 3. Foreground Service (The "Connecting..." spinner)
107
+ const foregroundServiceName = 'com.rnsnativecall.CallForegroundService';
108
+ if (!application.service.some(s => s.$['android:name'] === foregroundServiceName)) {
109
+ application.service.push({
110
+ $: {
111
+ 'android:name': foregroundServiceName,
112
+ 'android:foregroundServiceType': 'phoneCall',
113
+ 'android:exported': 'false'
114
+ }
115
+ });
116
+ }
117
+
102
118
  application.receiver = application.receiver || [];
103
119
  const receiverName = 'com.rnsnativecall.CallActionReceiver';
104
120
  if (!application.receiver.some(r => r.$['android:name'] === receiverName)) {