Skip to content

Commit 0650823

Browse files
committed
Add a timeout to end call when JS bridge crashes
1 parent 1c798d6 commit 0650823

File tree

7 files changed

+176
-54
lines changed

7 files changed

+176
-54
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ Alternative on iOS you can perform setup in `AppDelegate.m`. Doing this allows c
121121
If provided, the maximum number of calls in a single group, used for conferencing (Default: 1, no conferencing)
122122
- `supportsVideo`: boolean (optional)
123123
If provided, whether or not the application supports video calling (Default: true)
124+
- `displayCallReachabilityTimeout`: number in ms (optional)
125+
If provided, starts a timeout that check if the application is reachable and end the call if not (Default: null)
126+
You'll have to call `setReachable()` as soon as your Javascript application is started.
124127
- `android`: object
125128
- `alertTitle`: string (required)
126129
When asking for _phone account_ permission, we need to provider a title for the `Alert` to ask the user for it
@@ -139,8 +142,13 @@ Alternative on iOS you can perform setup in `AppDelegate.m`. Doing this allows c
139142
multiple popups to the user at different times.
140143
- `selfManaged`: boolean (optional)
141144
When set to true, call keep will configure itself to run as a self managed connection service. This is an advanced topic, and it's best to refer to [Googles Documentation](https://developer.android.com/guide/topics/connectivity/telecom/selfManaged) on the matter.
145+
- `displayCallReachabilityTimeout`: number in ms (optional)
146+
If provided, starts a timeout that check if the application is reachable and end the call if not (Default: null)
147+
You'll have to call `setReachable()` as soon as your Javascript application is started.
142148
143-
`setup` calls internally `registerPhoneAccount` and `registerEvents`.
149+
`setup` calls internally `registerPhoneAccount`, `registerEvents` and `setSettings`.
150+
151+
You can alternatively just call `setSettings()` with the same option as setup to define only your settings.
144152
145153
# Constants
146154

android/src/main/java/io/wazo/callkeep/RNCallKeepBackgroundMessagingService.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.facebook.react.bridge.WritableMap;
2626
import com.facebook.react.bridge.Arguments;
2727
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
28+
import com.facebook.react.jstasks.HeadlessJsRetryPolicy;
29+
import com.facebook.react.jstasks.LinearCountingRetryPolicy;
2830

2931
import static io.wazo.callkeep.Constants.EXTRA_CALLER_NAME;
3032
import static io.wazo.callkeep.Constants.EXTRA_CALL_NUMBER;
@@ -38,11 +40,17 @@ public class RNCallKeepBackgroundMessagingService extends HeadlessJsTaskService
3840
HeadlessJsTaskConfig getTaskConfig(Intent intent) {
3941
Bundle extras = intent.getExtras();
4042

43+
HeadlessJsRetryPolicy retryPolicy = new LinearCountingRetryPolicy(
44+
5, // Max number of retry attempts
45+
500 // Delay between each retry attempt
46+
);
47+
4148
return new HeadlessJsTaskConfig(
4249
"RNCallKeepBackgroundMessage",
4350
Arguments.fromBundle(extras),
4451
60000,
45-
false
52+
false,
53+
retryPolicy
4654
);
4755
}
4856
}

android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,12 @@ public void initializeTelecomManager() {
205205
telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
206206
}
207207

208+
@ReactMethod
208209
public void setSettings(ReadableMap options) {
209210
if (options == null) {
210211
return;
211212
}
213+
Log.d(TAG, "[VoiceConnection] setSettings: " + options);
212214
storeSettings(options);
213215

214216
this._settings = getSettings();
@@ -522,6 +524,11 @@ public void getInitialEvents(Promise promise) {
522524
promise.resolve(delayedEvents);
523525
}
524526

527+
@ReactMethod
528+
public void clearInitialEvents() {
529+
delayedEvents = new WritableNativeArray();
530+
}
531+
525532
@ReactMethod
526533
public void setOnHold(String uuid, boolean shouldHold) {
527534
Log.d(TAG, "[VoiceConnection] setOnHold, uuid: " + uuid + ", shouldHold: " + (shouldHold ? "true" : "false"));

android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,13 @@ public static void setAvailable(Boolean value) {
112112
isAvailable = value;
113113
}
114114

115-
public static ReadableMap getSettings() {
115+
public static WritableMap getSettings() {
116116
WritableMap settings = RNCallKeepModule.getInstanceSettings();
117+
return settings;
118+
}
119+
120+
public static ReadableMap getForegroundSettings() {
121+
WritableMap settings = VoiceConnectionService.getSettings();
117122
if (settings == null) {
118123
return null;
119124
}
@@ -178,18 +183,27 @@ public static void setState(String uuid, int state) {
178183

179184
@Override
180185
public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
181-
Bundle extra = request.getExtras();
186+
final Bundle extra = request.getExtras();
182187
Uri number = request.getAddress();
183188
String name = extra.getString(EXTRA_CALLER_NAME);
189+
String callUUID = extra.getString(EXTRA_CALL_UUID);
190+
Boolean isForeground = VoiceConnectionService.isRunning(this.getApplicationContext());
191+
WritableMap settings = this.getSettings();
192+
Integer timeout = settings.hasKey("displayCallReachabilityTimeout") ? settings.getInt("displayCallReachabilityTimeout") : null;
184193

185-
Log.d(TAG, "[VoiceConnectionService] onCreateIncomingConnection, name:" + name + ", number" + number);
194+
Log.d(TAG, "[VoiceConnectionService] onCreateIncomingConnection, name:" + name + ", number" + number +
195+
", isForeground: " + isForeground + ", isReachable:" + isReachable + ", timeout: " + timeout);
186196

187197
Connection incomingCallConnection = createConnection(request);
188198
incomingCallConnection.setRinging();
189199
incomingCallConnection.setInitialized();
190200

191201
startForegroundService();
192202

203+
if (timeout != null) {
204+
this.checkForAppReachability(callUUID, timeout);
205+
}
206+
193207
return incomingCallConnection;
194208
}
195209

@@ -270,7 +284,7 @@ private void startForegroundService() {
270284
return;
271285
}
272286
Log.d(TAG, "[VoiceConnectionService] startForegroundService");
273-
ReadableMap foregroundSettings = getSettings();
287+
ReadableMap foregroundSettings = getForegroundSettings();
274288

275289
if (foregroundSettings == null || !foregroundSettings.hasKey("channelId")) {
276290
Log.w(TAG, "[VoiceConnectionService] Not creating foregroundService because not configured");
@@ -306,7 +320,7 @@ private void startForegroundService() {
306320

307321
private void stopForegroundService() {
308322
Log.d(TAG, "[VoiceConnectionService] stopForegroundService");
309-
ReadableMap foregroundSettings = getSettings();
323+
ReadableMap foregroundSettings = getForegroundSettings();
310324

311325
if (foregroundSettings == null || !foregroundSettings.hasKey("channelId")) {
312326
Log.d(TAG, "[VoiceConnectionService] Discarding stop foreground service, no service configured");
@@ -517,4 +531,25 @@ public static boolean isRunning(Context context) {
517531

518532
return false;
519533
}
534+
535+
private void checkForAppReachability(final String callUUID, Integer timeout) {
536+
final VoiceConnectionService instance = this;
537+
538+
new android.os.Handler().postDelayed(new Runnable() {
539+
public void run() {
540+
if (instance.isReachable) {
541+
return;
542+
}
543+
Connection conn = VoiceConnectionService.getConnection(callUUID);
544+
Log.w(TAG, "[VoiceConnectionService] checkForAppReachability timeout, isReachable:" + instance.isReachable + ", uuid: " + callUUID);
545+
546+
if (conn == null) {
547+
Log.w(TAG, "[VoiceConnectionService] checkForAppReachability timeout, no connection to close with uuid: " + callUUID);
548+
549+
return;
550+
}
551+
conn.onDisconnect();
552+
}
553+
}, timeout);
554+
}
520555
}

index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ declare module 'react-native-callkeep' {
7171
export default class RNCallKeep {
7272
static getInitialEvents(): Promise<Array<Object>>
7373

74+
static clearInitialEvents(): void
75+
7476
static addEventListener(type: Events, handler: (args: any) => void): void
7577

7678
static removeEventListener(type: Events): void
@@ -135,6 +137,8 @@ declare module 'react-native-callkeep' {
135137

136138
static setReachable(): void
137139

140+
static setSettings(settings: Object): void;
141+
138142
/**
139143
* @description isCallActive method is available only on iOS.
140144
*/

index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class RNCallKeep {
4848
return this._setupIOS(options.ios);
4949
};
5050

51+
setSettings = (settings) => RNCallKeepModule.setSettings(settings[isIOS ? 'ios' : 'android']);
52+
5153
registerPhoneAccount = (options) => {
5254
if (isIOS) {
5355
return;
@@ -326,7 +328,7 @@ class RNCallKeep {
326328
},
327329
{ text: options.okButton, onPress: () => resolve(true) },
328330
],
329-
{ cancelable: true }
331+
{ cancelable: true },
330332
);
331333
});
332334

@@ -339,10 +341,11 @@ class RNCallKeep {
339341
}
340342

341343
getInitialEvents() {
342-
if (isIOS) {
343-
return RNCallKeepModule.getInitialEvents()
344-
}
345-
return Promise.resolve([])
344+
return RNCallKeepModule.getInitialEvents();
345+
}
346+
347+
clearInitialEvents() {
348+
return RNCallKeepModule.clearInitialEvents();
346349
}
347350
}
348351

0 commit comments

Comments
 (0)