Skip to content

[Android] Check if the application is reachable when making an outgoing call via the native application #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,17 @@ RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => {
- The digits that emit the dtmf tone
- `callUUID` (string)
- The UUID of the call.

### - checkReachability

On Android when the application is in background, after a certain delay the OS will close every connection with informing about it.
So we have to check if the application is reachable before making a call from the native phone application.

```js
RNCallKeep.addEventListener('checkReachability', () => {
RNCallKeep.setReachable();
});
```

## Example

Expand Down
10 changes: 7 additions & 3 deletions actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ const RNCallKeepDidPerformSetMutedCallAction = 'RNCallKeepDidPerformSetMutedCall
const RNCallKeepDidToggleHoldAction = 'RNCallKeepDidToggleHoldAction';
const RNCallKeepDidPerformDTMFAction = 'RNCallKeepDidPerformDTMFAction';
const RNCallKeepProviderReset = 'RNCallKeepProviderReset';
const RNCallKeepCheckReachability = 'RNCallKeepCheckReachability';
const isIOS = Platform.OS === 'ios';

const didReceiveStartCallAction = handler => {
eventEmitter.addListener(RNCallKeepDidReceiveStartCallAction, (data) => handler(data));

if (isIOS) {
// Tell CallKeep that we are ready to receive `RNCallKeepDidReceiveStartCallAction` event and prevent delay
RNCallKeepModule._startCallActionEventListenerAdded();
}

return eventEmitter.addListener(RNCallKeepDidReceiveStartCallAction, (data) => handler(data));
};

const answerCall = handler =>
Expand Down Expand Up @@ -51,6 +52,9 @@ const didPerformDTMFAction = handler =>
const didResetProvider = handler =>
eventEmitter.addListener(RNCallKeepProviderReset, handler);

const checkReachability = handler =>
eventEmitter.addListener(RNCallKeepCheckReachability, handler);

export const listeners = {
didReceiveStartCallAction,
answerCall,
Expand All @@ -62,5 +66,5 @@ export const listeners = {
didToggleHoldCallAction,
didPerformDTMFAction,
didResetProvider,
checkReachability,
};

13 changes: 10 additions & 3 deletions android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
public static final String ACTION_UNHOLD_CALL = "ACTION_UNHOLD_CALL";
public static final String ACTION_ONGOING_CALL = "ACTION_ONGOING_CALL";
public static final String ACTION_AUDIO_SESSION = "ACTION_AUDIO_SESSION";
public static final String ACTION_CHECK_REACHABILITY = "ACTION_CHECK_REACHABILITY";

private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String REACT_NATIVE_MODULE_NAME = "RNCallKeep";
Expand Down Expand Up @@ -360,6 +361,11 @@ public void setAvailable(Boolean active) {
VoiceConnectionService.setAvailable(active);
}

@ReactMethod
public void setReachable() {
VoiceConnectionService.setReachable();
}

@ReactMethod
public void setCurrentCallActive(String uuid) {
Connection conn = VoiceConnectionService.getConnection(uuid);
Expand Down Expand Up @@ -487,6 +493,7 @@ private void registerReceiver() {
intentFilter.addAction(ACTION_HOLD_CALL);
intentFilter.addAction(ACTION_ONGOING_CALL);
intentFilter.addAction(ACTION_AUDIO_SESSION);
intentFilter.addAction(ACTION_CHECK_REACHABILITY);
LocalBroadcastManager.getInstance(this.reactContext).registerReceiver(voiceBroadcastReceiver, intentFilter);
isReceiverRegistered = true;
}
Expand Down Expand Up @@ -514,9 +521,6 @@ private class VoiceBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
WritableMap args = Arguments.createMap();
HashMap<String, String> attributeMap = (HashMap<String, String>)intent.getSerializableExtra("attributeMap");
if (attributeMap == null) {
return;
}

switch (intent.getAction()) {
case ACTION_END_CALL:
Expand Down Expand Up @@ -561,6 +565,9 @@ public void onReceive(Context context, Intent intent) {
case ACTION_AUDIO_SESSION:
sendEventToJS("RNCallKeepDidActivateAudioSession", null);
break;
case ACTION_CHECK_REACHABILITY:
sendEventToJS("RNCallKeepCheckReachability", null);
break;
}
}
}
Expand Down
107 changes: 86 additions & 21 deletions android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import static io.wazo.callkeep.RNCallKeepModule.ACTION_ONGOING_CALL;
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL;
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNMUTE_CALL;
import static io.wazo.callkeep.RNCallKeepModule.ACTION_CHECK_REACHABILITY;
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALLER_NAME;
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALL_NUMBER;
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALL_UUID;
Expand All @@ -70,10 +71,15 @@
// @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionService.java
@TargetApi(Build.VERSION_CODES.M)
public class VoiceConnectionService extends ConnectionService {
private static Boolean isAvailable = false;
private static Boolean isAvailable;
private static Boolean isInitialized;
private static Boolean isReachable;
private static String notReachableCallUuid;
private static ConnectionRequest currentConnectionRequest;
private static String TAG = "RNCK:VoiceConnectionService";
public static Map<String, VoiceConnection> currentConnections = new HashMap<>();
public static Boolean hasOutgoingCall = false;
public static VoiceConnectionService currentConnectionService = null;

public static Connection getConnection(String connectionId) {
if (currentConnections.containsKey(connectionId)) {
Expand All @@ -85,12 +91,27 @@ public static Connection getConnection(String connectionId) {
public VoiceConnectionService() {
super();
Log.e(TAG, "Constructor");
isReachable = false;
isInitialized = false;
isAvailable = false;
currentConnectionRequest = null;
currentConnectionService = this;
}

public static void setAvailable(Boolean value) {
Log.d(TAG, "setAvailable: " + (value ? "true" : "false"));
if (value) {
isInitialized = true;
}

isAvailable = value;
}

public static void setReachable() {
Log.d(TAG, "setReachable");
isReachable = true;
VoiceConnectionService.currentConnectionRequest = null;
}

public static void deinitConnection(String connectionId) {
Log.d(TAG, "deinitConnection:" + connectionId);
Expand All @@ -116,44 +137,44 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage
@Override
public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
VoiceConnectionService.hasOutgoingCall = true;
String uuid = UUID.randomUUID().toString();

if (!isInitialized && !isReachable) {
this.notReachableCallUuid = uuid;
this.currentConnectionRequest = request;
this.checkReachability();
}

return this.makeOutgoingCall(request, uuid, false);
}

private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Boolean forceWakeUp) {
Bundle extras = request.getExtras();
Connection outgoingCallConnection = null;
String number = request.getAddress().getSchemeSpecificPart();
String extrasNumber = extras.getString(EXTRA_CALL_NUMBER);
String displayName = extras.getString(EXTRA_CALLER_NAME);
String uuid = UUID.randomUUID().toString();
Boolean isForeground = VoiceConnectionService.isRunning(this.getApplicationContext());

Log.d(TAG, "onCreateOutgoingConnection:" + uuid + ", number: " + number);
Log.d(TAG, "makeOutgoingCall:" + uuid + ", number: " + number + ", displayName:" + displayName);

// Wakeup application if needed
if (!VoiceConnectionService.isRunning(this.getApplicationContext())) {
if (!isForeground || forceWakeUp) {
Log.d(TAG, "onCreateOutgoingConnection: Waking up application");
Intent headlessIntent = new Intent(
this.getApplicationContext(),
RNCallKeepBackgroundMessagingService.class
);
headlessIntent.putExtra("callUUID", uuid);
headlessIntent.putExtra("name", displayName);
headlessIntent.putExtra("handle", number);
ComponentName name = this.getApplicationContext().startService(headlessIntent);
if (name != null) {
HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext());
}
} else if (!this.canMakeOutgoingCall()) {
this.wakeUpApplication(uuid, number, displayName);
} else if (!this.canMakeOutgoingCall() && isReachable) {
Log.d(TAG, "onCreateOutgoingConnection: not available");
return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.LOCAL));
}

// TODO: Hold all other calls
if (extrasNumber != null && extrasNumber.equals(number)) {
outgoingCallConnection = createConnection(request);
} else {
if (extrasNumber == null || !extrasNumber.equals(number)) {
extras.putString(EXTRA_CALL_UUID, uuid);
extras.putString(EXTRA_CALLER_NAME, displayName);
extras.putString(EXTRA_CALL_NUMBER, number);
outgoingCallConnection = createConnection(request);
}

outgoingCallConnection = createConnection(request);
outgoingCallConnection.setDialing();
outgoingCallConnection.setAudioModeIsVoip(true);
outgoingCallConnection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED);
Expand All @@ -164,15 +185,59 @@ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManage
sendCallRequestToActivity(ACTION_ONGOING_CALL, extrasMap);
sendCallRequestToActivity(ACTION_AUDIO_SESSION, null);

Log.d(TAG, "onCreateOutgoingConnection: calling");

return outgoingCallConnection;
}

private void wakeUpApplication(String uuid, String number, String displayName) {
Intent headlessIntent = new Intent(
this.getApplicationContext(),
RNCallKeepBackgroundMessagingService.class
);
headlessIntent.putExtra("callUUID", uuid);
headlessIntent.putExtra("name", displayName);
headlessIntent.putExtra("handle", number);
Log.d(TAG, "wakeUpApplication: " + uuid + ", number : " + number + ", displayName:" + displayName);

ComponentName name = this.getApplicationContext().startService(headlessIntent);
if (name != null) {
HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext());
}
}

private void wakeUpAfterReachabilityTimeout(ConnectionRequest request) {
if (this.currentConnectionRequest == null) {
return;
}
Log.d(TAG, "checkReachability timeout, force wakeup");
Bundle extras = request.getExtras();
String number = request.getAddress().getSchemeSpecificPart();
String displayName = extras.getString(EXTRA_CALLER_NAME);
wakeUpApplication(this.notReachableCallUuid, number, displayName);

VoiceConnectionService.currentConnectionRequest = null;
}

private void checkReachability() {
Log.d(TAG, "checkReachability");

final VoiceConnectionService instance = this;
sendCallRequestToActivity(ACTION_CHECK_REACHABILITY, null);

new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
instance.wakeUpAfterReachabilityTimeout(instance.currentConnectionRequest);
}
}, 2000);
}

private Boolean canMakeOutgoingCall() {
return isAvailable;
}

private Connection createConnection(ConnectionRequest request) {

Bundle extras = request.getExtras();
HashMap<String, String> extrasMap = this.bundleToMap(extras);
extrasMap.put(EXTRA_CALL_NUMBER, request.getAddress().toString());
Expand Down
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type Events =
'didToggleHoldCallAction' |
'didPerformDTMFAction' |
'didResetProvider' |
'checkReachability' |
'didPerformSetMutedCallAction';

type HandleType = 'generic' | 'number' | 'email';
Expand Down Expand Up @@ -110,6 +111,10 @@ export default class RNCallKeep {

}

static setReachable() {

}

/**
* @description supportConnectionService method is available only on Android.
*/
Expand Down
18 changes: 9 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,23 @@ const supportConnectionService = !isIOS && Platform.Version >= 23;
class RNCallKeep {

constructor() {
this._callkitEventHandlers = new Map();
this._callkeepEventHandlers = new Map();
}


addEventListener = (type, handler) => {
const listener = listeners[type](handler);

this._callkitEventHandlers.set(handler, listener);
this._callkeepEventHandlers.set(type, listener);
};

removeEventListener = (type, handler) => {
const listener = this._callkitEventHandlers.get(handler);
removeEventListener = (type) => {
const listener = this._callkeepEventHandlers.get(type);
if (!listener) {
return;
}

listener.remove();
this._callkitEventHandlers.delete(handler);
this._callkeepEventHandlers.delete(type);
};

setup = async (options) => {
Expand Down Expand Up @@ -83,8 +82,7 @@ class RNCallKeep {
}
};

reportEndCallWithUUID = (uuid, reason) =>
RNCallKeepModule.reportEndCallWithUUID(uuid, reason);
reportEndCallWithUUID = (uuid, reason) => RNCallKeepModule.reportEndCallWithUUID(uuid, reason);

/*
* Android explicitly states we reject a call
Expand Down Expand Up @@ -147,9 +145,11 @@ class RNCallKeep {

setOnHold = (uuid, shouldHold) => RNCallKeepModule.setOnHold(uuid, shouldHold);

setReachable = () => RNCallKeepModule.setReachable();

// @deprecated
reportUpdatedCall = (uuid, localizedCallerName) => {
console.warn('RNCallKeep.reportUpdatedCall is deprecarted, use RNCallKeep.updateDisplay instead');
console.warn('RNCallKeep.reportUpdatedCall is deprecated, use RNCallKeep.updateDisplay instead');

return isIOS
? RNCallKeepModule.reportUpdatedCall(uuid, localizedCallerName)
Expand Down
4 changes: 3 additions & 1 deletion ios/RNCallKeep/RNCallKeep.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
static NSString *const RNCallKeepPerformPlayDTMFCallAction = @"RNCallKeepDidPerformDTMFAction";
static NSString *const RNCallKeepDidToggleHoldAction = @"RNCallKeepDidToggleHoldAction";
static NSString *const RNCallKeepProviderReset = @"RNCallKeepProviderReset";
static NSString *const RNCallKeepCheckReachability = @"RNCallKeepCheckReachability";

@implementation RNCallKeep
{
Expand Down Expand Up @@ -90,7 +91,8 @@ - (void)dealloc
RNCallKeepDidPerformSetMutedCallAction,
RNCallKeepPerformPlayDTMFCallAction,
RNCallKeepDidToggleHoldAction,
RNCallKeepProviderReset
RNCallKeepProviderReset,
RNCallKeepCheckReachability
];
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-callkeep",
"version": "3.0.1",
"version": "3.0.2",
"description": "iOS 10 CallKit and Android ConnectionService Framework For React Native",
"main": "index.js",
"scripts": {},
Expand Down