Skip to content

Commit

Permalink
Add a timeout to end call when JS bridge crashes
Browse files Browse the repository at this point in the history
  • Loading branch information
manuquentin committed Feb 16, 2022
1 parent 1c798d6 commit 0650823
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 54 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ Alternative on iOS you can perform setup in `AppDelegate.m`. Doing this allows c
If provided, the maximum number of calls in a single group, used for conferencing (Default: 1, no conferencing)
- `supportsVideo`: boolean (optional)
If provided, whether or not the application supports video calling (Default: true)
- `displayCallReachabilityTimeout`: number in ms (optional)
If provided, starts a timeout that check if the application is reachable and end the call if not (Default: null)
You'll have to call `setReachable()` as soon as your Javascript application is started.
- `android`: object
- `alertTitle`: string (required)
When asking for _phone account_ permission, we need to provider a title for the `Alert` to ask the user for it
Expand All @@ -139,8 +142,13 @@ Alternative on iOS you can perform setup in `AppDelegate.m`. Doing this allows c
multiple popups to the user at different times.
- `selfManaged`: boolean (optional)
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.
- `displayCallReachabilityTimeout`: number in ms (optional)
If provided, starts a timeout that check if the application is reachable and end the call if not (Default: null)
You'll have to call `setReachable()` as soon as your Javascript application is started.
`setup` calls internally `registerPhoneAccount` and `registerEvents`.
`setup` calls internally `registerPhoneAccount`, `registerEvents` and `setSettings`.
You can alternatively just call `setSettings()` with the same option as setup to define only your settings.
# Constants
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import com.facebook.react.jstasks.HeadlessJsRetryPolicy;
import com.facebook.react.jstasks.LinearCountingRetryPolicy;

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

HeadlessJsRetryPolicy retryPolicy = new LinearCountingRetryPolicy(
5, // Max number of retry attempts
500 // Delay between each retry attempt
);

return new HeadlessJsTaskConfig(
"RNCallKeepBackgroundMessage",
Arguments.fromBundle(extras),
60000,
false
false,
retryPolicy
);
}
}
7 changes: 7 additions & 0 deletions android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,12 @@ public void initializeTelecomManager() {
telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
}

@ReactMethod
public void setSettings(ReadableMap options) {
if (options == null) {
return;
}
Log.d(TAG, "[VoiceConnection] setSettings: " + options);
storeSettings(options);

this._settings = getSettings();
Expand Down Expand Up @@ -522,6 +524,11 @@ public void getInitialEvents(Promise promise) {
promise.resolve(delayedEvents);
}

@ReactMethod
public void clearInitialEvents() {
delayedEvents = new WritableNativeArray();
}

@ReactMethod
public void setOnHold(String uuid, boolean shouldHold) {
Log.d(TAG, "[VoiceConnection] setOnHold, uuid: " + uuid + ", shouldHold: " + (shouldHold ? "true" : "false"));
Expand Down
45 changes: 40 additions & 5 deletions android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,13 @@ public static void setAvailable(Boolean value) {
isAvailable = value;
}

public static ReadableMap getSettings() {
public static WritableMap getSettings() {
WritableMap settings = RNCallKeepModule.getInstanceSettings();
return settings;
}

public static ReadableMap getForegroundSettings() {
WritableMap settings = VoiceConnectionService.getSettings();
if (settings == null) {
return null;
}
Expand Down Expand Up @@ -178,18 +183,27 @@ public static void setState(String uuid, int state) {

@Override
public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
Bundle extra = request.getExtras();
final Bundle extra = request.getExtras();
Uri number = request.getAddress();
String name = extra.getString(EXTRA_CALLER_NAME);
String callUUID = extra.getString(EXTRA_CALL_UUID);
Boolean isForeground = VoiceConnectionService.isRunning(this.getApplicationContext());
WritableMap settings = this.getSettings();
Integer timeout = settings.hasKey("displayCallReachabilityTimeout") ? settings.getInt("displayCallReachabilityTimeout") : null;

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

Connection incomingCallConnection = createConnection(request);
incomingCallConnection.setRinging();
incomingCallConnection.setInitialized();

startForegroundService();

if (timeout != null) {
this.checkForAppReachability(callUUID, timeout);
}

return incomingCallConnection;
}

Expand Down Expand Up @@ -270,7 +284,7 @@ private void startForegroundService() {
return;
}
Log.d(TAG, "[VoiceConnectionService] startForegroundService");
ReadableMap foregroundSettings = getSettings();
ReadableMap foregroundSettings = getForegroundSettings();

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

private void stopForegroundService() {
Log.d(TAG, "[VoiceConnectionService] stopForegroundService");
ReadableMap foregroundSettings = getSettings();
ReadableMap foregroundSettings = getForegroundSettings();

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

return false;
}

private void checkForAppReachability(final String callUUID, Integer timeout) {
final VoiceConnectionService instance = this;

new android.os.Handler().postDelayed(new Runnable() {
public void run() {
if (instance.isReachable) {
return;
}
Connection conn = VoiceConnectionService.getConnection(callUUID);
Log.w(TAG, "[VoiceConnectionService] checkForAppReachability timeout, isReachable:" + instance.isReachable + ", uuid: " + callUUID);

if (conn == null) {
Log.w(TAG, "[VoiceConnectionService] checkForAppReachability timeout, no connection to close with uuid: " + callUUID);

return;
}
conn.onDisconnect();
}
}, timeout);
}
}
4 changes: 4 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ declare module 'react-native-callkeep' {
export default class RNCallKeep {
static getInitialEvents(): Promise<Array<Object>>

static clearInitialEvents(): void

static addEventListener(type: Events, handler: (args: any) => void): void

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

static setReachable(): void

static setSettings(settings: Object): void;

/**
* @description isCallActive method is available only on iOS.
*/
Expand Down
13 changes: 8 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class RNCallKeep {
return this._setupIOS(options.ios);
};

setSettings = (settings) => RNCallKeepModule.setSettings(settings[isIOS ? 'ios' : 'android']);

registerPhoneAccount = (options) => {
if (isIOS) {
return;
Expand Down Expand Up @@ -326,7 +328,7 @@ class RNCallKeep {
},
{ text: options.okButton, onPress: () => resolve(true) },
],
{ cancelable: true }
{ cancelable: true },
);
});

Expand All @@ -339,10 +341,11 @@ class RNCallKeep {
}

getInitialEvents() {
if (isIOS) {
return RNCallKeepModule.getInitialEvents()
}
return Promise.resolve([])
return RNCallKeepModule.getInitialEvents();
}

clearInitialEvents() {
return RNCallKeepModule.clearInitialEvents();
}
}

Expand Down
Loading

0 comments on commit 0650823

Please sign in to comment.