rns-nativecall 0.6.2 → 0.6.4
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.
- package/README.md +77 -157
- package/android/build.gradle +0 -3
- package/android/src/main/java/com/rnsnativecall/AcceptCallActivity.kt +38 -38
- package/android/src/main/java/com/rnsnativecall/CallActionReceiver.kt +32 -27
- package/android/src/main/java/com/rnsnativecall/CallHeadlessTask.kt +21 -49
- package/android/src/main/java/com/rnsnativecall/CallMessagingService.kt +87 -83
- package/android/src/main/java/com/rnsnativecall/CallModule.kt +90 -21
- package/android/src/main/java/com/rnsnativecall/CallState.kt +54 -0
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +93 -53
- package/android/src/main/java/com/rnsnativecall/UnlockPromptActivity.kt +55 -0
- package/app.plugin.js +1 -1
- package/index.d.ts +13 -26
- package/index.js +36 -10
- package/package.json +12 -24
- package/rns-nativecall.podspec +1 -1
- package/withNativeCallVoip.js +94 -80
package/README.md
CHANGED
|
@@ -1,174 +1,94 @@
|
|
|
1
|
-
#
|
|
1
|
+
# rns-nativecall
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A professional VoIP incoming call handler for React Native. Features a "Single Call Gate" on Android to manage busy states and full CallKit integration for iOS.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Display incoming calls with native UI.
|
|
10
|
-
* Support for video and audio calls.
|
|
11
|
-
* Self-managed calls on Android (prevents polluting the device call log).
|
|
12
|
-
* Prevents iOS call log from being saved using `includesCallsInRecents = NO`.
|
|
13
|
-
* Subscribe to call events (accepted, rejected, failed).
|
|
14
|
-
* Cross-platform (Android & iOS) ready.
|
|
5
|
+
## 🚀 Highlights
|
|
6
|
+
- **Expo Support**: Built-in config plugin handles all native setup.
|
|
7
|
+
- **Single Call Gate**: Automatically detects if the user is in a call and flags secondary calls as "BUSY".
|
|
8
|
+
- **Headless Mode**: Works even when the app is killed or the screen is locked.
|
|
15
9
|
|
|
16
10
|
---
|
|
17
11
|
|
|
18
|
-
## Installation
|
|
12
|
+
## 📦 Installation
|
|
19
13
|
|
|
20
14
|
```bash
|
|
21
15
|
npm install rns-nativecall
|
|
22
|
-
# or
|
|
23
|
-
yarn add rns-nativecall
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Expo Integration
|
|
29
16
|
|
|
30
|
-
If you are using **Expo managed workflow**, the package provides a plugin that automatically modifies the `AndroidManifest.xml` for required permissions and services.
|
|
31
|
-
|
|
32
|
-
In your `app.json` / `app.config.js`:
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
export default {
|
|
36
|
-
expo: {
|
|
37
|
-
name: 'YourApp',
|
|
38
|
-
slug: 'your-app',
|
|
39
|
-
plugins: [
|
|
40
|
-
"rns-nativecall"
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
17
|
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
> It also registers the `MyConnectionService` for Telecom integration:
|
|
54
|
-
>
|
|
55
|
-
> ```xml
|
|
56
|
-
> <service
|
|
57
|
-
> android:name="com.rnsnativecall.MyConnectionService"
|
|
58
|
-
> android:permission="android.permission.BIND_CONNECTION_SERVICE"
|
|
59
|
-
> android:exported="true">
|
|
60
|
-
> <intent-filter>
|
|
61
|
-
> <action android:name="android.telecom.ConnectionService"/>
|
|
62
|
-
> </intent-filter>
|
|
63
|
-
> </service>
|
|
64
|
-
> ```
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## iOS Setup
|
|
69
|
-
|
|
70
|
-
1. Make sure you have the pod installed:
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
cd ios
|
|
74
|
-
pod install
|
|
18
|
+
---
|
|
19
|
+
Add the plugin to your app.json or app.config.js:
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"expo": {
|
|
23
|
+
"plugins": ["rns-nativecall"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
75
26
|
```
|
|
76
|
-
|
|
77
|
-
2. CallKit is automatically used, so you need to enable it in your project, thats all and call log entries are prevented using:
|
|
78
|
-
|
|
79
|
-
```objc
|
|
80
|
-
config.includesCallsInRecents = NO;
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
> The native iOS module handles showing the call UI and managing call events.
|
|
84
|
-
|
|
85
27
|
---
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
28
|
+
### 🛠 Usage
|
|
29
|
+
|
|
30
|
+
1. Initialize Headless Task (index.js)
|
|
31
|
+
Register the task at the very top of your entry file. This handles background calls and "Busy" signals for secondary callers.
|
|
32
|
+
```javascript
|
|
33
|
+
import { AppRegistry } from 'react-native';
|
|
34
|
+
import App from './App';
|
|
35
|
+
import { CallHandler } from 'rns-nativecall';
|
|
36
|
+
|
|
37
|
+
CallHandler.registerHeadlessTask(async (data, eventType) => {
|
|
38
|
+
if (eventType === 'BUSY') {
|
|
39
|
+
// User is already on a call. Notify the second caller via WebSocket/API.
|
|
40
|
+
console.log("System Busy for UUID:", data.callUuid);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (eventType === 'INCOMING_CALL') {
|
|
45
|
+
// App is waking up for a new call.
|
|
46
|
+
// Trigger your custom UI or logic here.
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
AppRegistry.registerComponent('main', () => App);
|
|
51
|
+
```
|
|
52
|
+
### 2. Handling Events (App.js)
|
|
53
|
+
Use the subscribe method to handle user interaction (Accept/Reject) from the native UI.
|
|
54
|
+
```javascript
|
|
55
|
+
import React, { useEffect } from 'react';
|
|
56
|
+
import { CallHandler } from 'rns-nativecall';
|
|
57
|
+
|
|
58
|
+
export default function App() {
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const unsubscribe = CallHandler.subscribe(
|
|
61
|
+
(data) => {
|
|
62
|
+
console.log("Call Accepted:", data.callUuid);
|
|
63
|
+
// Navigate to your call screen
|
|
64
|
+
},
|
|
65
|
+
(data) => {
|
|
66
|
+
console.log("Call Rejected:", data.callUuid);
|
|
67
|
+
// Send 'Hangup' signal to your server
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return () => unsubscribe();
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
// To manually trigger the UI (e.g., from an FCM data message)
|
|
75
|
+
const showCall = () => {
|
|
76
|
+
CallHandler.displayCall("unique-uuid", "Caller Name", "video");
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return <YourUI />;
|
|
80
|
+
}
|
|
113
81
|
```
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## API
|
|
118
|
-
|
|
119
|
-
### `displayCall(uuid, number, name, hasVideo?, shouldRing?) => Promise<boolean>`
|
|
120
|
-
|
|
121
|
-
Displays native incoming call UI.
|
|
122
|
-
|
|
123
|
-
* **uuid**: Unique call identifier.
|
|
124
|
-
* **number**: Caller number.
|
|
125
|
-
* **name**: Caller display name.
|
|
126
|
-
* **hasVideo**: Boolean, true for video call.
|
|
127
|
-
* **shouldRing**: Boolean, true to play native ringtone.
|
|
128
|
-
|
|
129
|
-
Returns a promise that resolves to `true` if UI was successfully displayed.
|
|
130
|
-
|
|
131
|
-
### `destroyNativeCallUI(uuid)`
|
|
132
|
-
|
|
133
|
-
Dismisses the native call UI.
|
|
134
|
-
|
|
135
|
-
* **uuid**: Call identifier.
|
|
136
|
-
|
|
137
|
-
### `subscribe(onAccept, onReject, onFailed) => () => void`
|
|
138
|
-
|
|
139
|
-
Subscribe to native call events.
|
|
140
|
-
|
|
141
|
-
* **onAccept**: Callback for accepted calls.
|
|
142
|
-
* **onReject**: Callback for rejected calls.
|
|
143
|
-
* **onFailed**: Optional callback for failed calls.
|
|
144
|
-
|
|
145
|
-
Returns a function to unsubscribe all listeners.
|
|
146
|
-
|
|
147
82
|
---
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
The Expo plugin ensures these are automatically added to `AndroidManifest.xml`.
|
|
157
|
-
|
|
158
|
-
### iOS
|
|
159
|
-
|
|
160
|
-
* Uses CallKit (`CXProvider`) internally.
|
|
161
|
-
* Prevents call log pollution using `includesCallsInRecents = NO`.
|
|
162
|
-
|
|
83
|
+
### 📖 API Reference
|
|
84
|
+
| Method | Description |
|
|
85
|
+
| :--- | :--- |
|
|
86
|
+
| **registerHeadlessTask(callback)** | Registers background logic. `eventType` is `'INCOMING_CALL'` or `'BUSY'`. |
|
|
87
|
+
| **displayCall(uuid, name, type)** | Shows the native call UI. Type is `'audio'` or `'video'`. |
|
|
88
|
+
| **destroyNativeCallUI(uuid)** | Dismisses the native call interface. |
|
|
89
|
+
| **getInitialCallData()** | Returns call data if the app was launched by clicking "Answer". |
|
|
90
|
+
| **subscribe(onAccept, onReject)** | Listens for native button presses (Answer/End). |
|
|
163
91
|
---
|
|
164
92
|
|
|
165
|
-
##
|
|
166
|
-
|
|
167
|
-
* Android uses `Connection.CAPABILITY_SELF_MANAGED` to prevent calls from showing in the system call log.
|
|
168
|
-
* iOS uses `CXCallEndedReasonRemoteEnded` for clean UI dismissal without showing failed call overlays.
|
|
169
|
-
|
|
93
|
+
## 🛡 License
|
|
170
94
|
---
|
|
171
|
-
|
|
172
|
-
## License
|
|
173
|
-
|
|
174
|
-
MIT
|
package/android/build.gradle
CHANGED
|
@@ -68,7 +68,4 @@ dependencies {
|
|
|
68
68
|
// Glide for profile pictures
|
|
69
69
|
implementation "com.github.bumptech.glide:glide:4.15.1"
|
|
70
70
|
annotationProcessor "com.github.bumptech.glide:compiler:4.15.1"
|
|
71
|
-
|
|
72
|
-
implementation "androidx.core:core-ktx:1.12.0"
|
|
73
|
-
implementation "androidx.appcompat:appcompat:1.6.1"
|
|
74
71
|
}
|
|
@@ -5,51 +5,51 @@ import android.app.NotificationManager
|
|
|
5
5
|
import android.content.Context
|
|
6
6
|
import android.content.Intent
|
|
7
7
|
import android.os.Bundle
|
|
8
|
+
import android.view.WindowManager
|
|
9
|
+
import android.app.KeyguardManager
|
|
8
10
|
|
|
9
11
|
class AcceptCallActivity : Activity() {
|
|
12
|
+
|
|
10
13
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
11
14
|
super.onCreate(savedInstanceState)
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
window.addFlags(
|
|
17
|
+
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
18
|
+
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
|
|
19
|
+
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
23
|
+
keyguardManager.requestDismissKeyguard(this, null)
|
|
24
|
+
|
|
25
|
+
processCallIntent(intent)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private fun processCallIntent(intent: Intent) {
|
|
29
|
+
NativeCallManager.stopRingtone()
|
|
30
|
+
|
|
14
31
|
val extras = intent.extras
|
|
32
|
+
val uuid = extras?.getString("callUuid")
|
|
15
33
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
val value = extras.get(key)
|
|
19
|
-
if (value != null) {
|
|
20
|
-
dataMap[key] = value.toString()
|
|
21
|
-
}
|
|
22
|
-
}
|
|
34
|
+
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
35
|
+
uuid?.let { notificationManager.cancel(it.hashCode()) }
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Kill background processes
|
|
28
|
-
CallMessagingService.stopBackupTimer(uuid)
|
|
29
|
-
val stopHeadlessIntent = Intent(applicationContext, CallHeadlessTask::class.java)
|
|
30
|
-
applicationContext.stopService(stopHeadlessIntent)
|
|
31
|
-
|
|
32
|
-
// Stop Ringtone & Notification
|
|
33
|
-
NativeCallManager.stopRingtone()
|
|
34
|
-
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
35
|
-
notificationManager.cancel(uuid.hashCode())
|
|
36
|
-
|
|
37
|
-
// SYNC WITH JS
|
|
38
|
-
CallModule.setPendingCallData(dataMap)
|
|
39
|
-
CallModule.sendEventToJS("onCallAccepted", dataMap)
|
|
40
|
-
|
|
41
|
-
// OPEN THE APP
|
|
42
|
-
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
43
|
-
launchIntent?.apply {
|
|
44
|
-
// Pass ALL data forward to the main app
|
|
45
|
-
putExtras(extras ?: Bundle())
|
|
46
|
-
putExtra("navigatingToCall", true)
|
|
47
|
-
|
|
48
|
-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
49
|
-
}
|
|
50
|
-
startActivity(launchIntent)
|
|
51
|
-
}
|
|
52
|
-
|
|
37
|
+
// ✅ WE STOP SENDING THE JS EVENT HERE.
|
|
38
|
+
// Instead, we pass the intent to MainActivity with a specific ACTION.
|
|
39
|
+
openMainApp(extras)
|
|
53
40
|
finish()
|
|
54
41
|
}
|
|
42
|
+
|
|
43
|
+
private fun openMainApp(extras: Bundle?) {
|
|
44
|
+
// We look for the MainActivity class specifically
|
|
45
|
+
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
46
|
+
launchIntent?.apply {
|
|
47
|
+
// ✅ CRITICAL: Identify this as a deliberate "Answer" click
|
|
48
|
+
action = "com.rnsnativecall.ACTION_ANSWER"
|
|
49
|
+
|
|
50
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
51
|
+
putExtras(extras ?: Bundle())
|
|
52
|
+
startActivity(this)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
55
|
}
|
|
@@ -4,44 +4,49 @@ import android.content.BroadcastReceiver
|
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.content.Intent
|
|
6
6
|
import android.app.NotificationManager
|
|
7
|
+
import android.app.KeyguardManager
|
|
7
8
|
|
|
8
9
|
class CallActionReceiver : BroadcastReceiver() {
|
|
9
10
|
override fun onReceive(context: Context, intent: Intent) {
|
|
10
|
-
|
|
11
|
-
val action = intent.action ?: return
|
|
12
|
-
|
|
13
|
-
// 1. KILL THE BACKUP TIMER (Stop MessagingService from re-triggering)
|
|
14
|
-
CallMessagingService.stopBackupTimer(uuid)
|
|
11
|
+
NativeCallManager.stopRingtone()
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
val
|
|
18
|
-
context.stopService(stopHeadlessIntent)
|
|
13
|
+
val uuid = intent.getStringExtra("EXTRA_CALL_UUID") ?: return
|
|
14
|
+
val action = intent.action ?: ""
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
23
|
-
notificationManager.cancel(uuid.hashCode())
|
|
16
|
+
val notificationManager =
|
|
17
|
+
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
24
18
|
|
|
25
19
|
val dataMap = mutableMapOf<String, String>()
|
|
26
20
|
intent.extras?.keySet()?.forEach { key ->
|
|
27
21
|
intent.extras?.get(key)?.let { dataMap[key] = it.toString() }
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
val name = intent.extras?.getString("name") ?: "Someone"
|
|
25
|
+
val callType = intent.extras?.getString("callType") ?: "audio"
|
|
26
|
+
|
|
27
|
+
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
28
|
+
|
|
29
|
+
if (action.contains("ACTION_REJECT")) {
|
|
30
|
+
if (CallModule.isReady()) {
|
|
31
|
+
CallModule.sendEventToJS("onCallRejected", mapOf("callUuid" to uuid))
|
|
32
|
+
notificationManager.cancel(uuid.hashCode())
|
|
33
|
+
} else {
|
|
34
|
+
// Queue pending event
|
|
35
|
+
CallModule.setPendingCallData("onCallRejected_pending", mapOf("callUuid" to uuid))
|
|
36
|
+
// Update notification pill to "Aborting…" state
|
|
37
|
+
NativeCallManager.aborting(context, uuid, name, callType)
|
|
38
|
+
|
|
39
|
+
// Register callback to auto‑open app once RN is ready
|
|
40
|
+
CallModule.registerOnReadyCallback {
|
|
41
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
42
|
+
launchIntent?.apply {
|
|
43
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
44
|
+
putExtras(intent.extras ?: android.os.Bundle())
|
|
45
|
+
putExtra("navigatingToCall", true)
|
|
46
|
+
context.startActivity(this)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
39
49
|
}
|
|
40
|
-
context.startActivity(launchIntent)
|
|
41
|
-
|
|
42
|
-
} else if (action.contains("ACTION_REJECT")) {
|
|
43
|
-
// Tell JS call is dead
|
|
44
|
-
CallModule.sendEventToJS("onCallRejected", dataMap)
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
|
-
}
|
|
52
|
+
}
|
|
@@ -1,64 +1,36 @@
|
|
|
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.content.Context
|
|
7
3
|
import android.content.Intent
|
|
8
|
-
import android.content.pm.ServiceInfo
|
|
9
|
-
import android.os.Build
|
|
10
|
-
import androidx.core.app.NotificationCompat
|
|
11
4
|
import com.facebook.react.HeadlessJsTaskService
|
|
12
5
|
import com.facebook.react.bridge.Arguments
|
|
13
6
|
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
|
14
7
|
import com.facebook.react.jstasks.LinearCountingRetryPolicy
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
import com.rnsnativecall.CallState
|
|
17
10
|
|
|
18
|
-
override fun onCreate() {
|
|
19
|
-
super.onCreate()
|
|
20
|
-
val channelId = "call_service"
|
|
21
|
-
val notificationId = 1 // Constant ID for the foreground service
|
|
22
11
|
|
|
23
|
-
|
|
24
|
-
val channel = NotificationChannel(
|
|
25
|
-
channelId,
|
|
26
|
-
"Call Connection Service",
|
|
27
|
-
NotificationManager.IMPORTANCE_LOW
|
|
28
|
-
)
|
|
29
|
-
val manager = getSystemService(NotificationManager::class.java)
|
|
30
|
-
manager.createNotificationChannel(channel)
|
|
31
|
-
}
|
|
12
|
+
class CallHeadlessTask : HeadlessJsTaskService() {
|
|
32
13
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
.setSmallIcon(android.R.drawable.sym_action_call) // Built-in icon as fallback
|
|
38
|
-
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
39
|
-
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
40
|
-
.build()
|
|
14
|
+
override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
|
|
15
|
+
val extras = intent?.extras ?: return null
|
|
16
|
+
val uuid = extras.getString("callUuid")
|
|
17
|
+
val isBusySignal = extras.getBoolean("isBusySignal", false)
|
|
41
18
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
startForeground(notificationId, notification)
|
|
47
|
-
}
|
|
19
|
+
// GATE: If the call was canceled before we even started the task,
|
|
20
|
+
// we don't need to show the UI OR send a busy signal.
|
|
21
|
+
if (CallState.isCanceled(uuid)) {
|
|
22
|
+
return null
|
|
48
23
|
}
|
|
49
24
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
} else null
|
|
63
|
-
}
|
|
25
|
+
// Use a shorter timeout for Busy Signals (30s) vs real calls (60s)
|
|
26
|
+
val timeout = if (isBusySignal) 30000L else 60000L
|
|
27
|
+
|
|
28
|
+
return HeadlessJsTaskConfig(
|
|
29
|
+
"ColdStartCallTask",
|
|
30
|
+
Arguments.fromBundle(extras),
|
|
31
|
+
timeout,
|
|
32
|
+
true, // Allow in foreground
|
|
33
|
+
LinearCountingRetryPolicy(3, 1000)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
64
36
|
}
|