Skip to content

Commit c74aacd

Browse files
committed
[Android] Introduce checkReachability event
1 parent 824a602 commit c74aacd

File tree

6 files changed

+137
-41
lines changed

6 files changed

+137
-41
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,22 @@ RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => {
470470
});
471471
```
472472

473+
- `digits` (string)
474+
- The digits that emit the dtmf tone
475+
- `callUUID` (string)
476+
- The UUID of the call.
477+
478+
### - checkReachability
479+
480+
On Android when the application is in background, after a certain delay the OS will close every connection with informing about it.
481+
So we have to check if the application is reachable before making a call from the native phone application.
482+
483+
```js
484+
RNCallKeep.addEventListener('checkReachability', () => {
485+
RNCallKeep.setReachable();
486+
});
487+
```
488+
473489
- `digits` (string)
474490
- The digits that emit the dtmf tone
475491
- `callUUID` (string)

actions.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ const RNCallKeepDidPerformSetMutedCallAction = 'RNCallKeepDidPerformSetMutedCall
1313
const RNCallKeepDidToggleHoldAction = 'RNCallKeepDidToggleHoldAction';
1414
const RNCallKeepDidPerformDTMFAction = 'RNCallKeepDidPerformDTMFAction';
1515
const RNCallKeepProviderReset = 'RNCallKeepProviderReset';
16+
const RNCallKeepCheckReachability = 'RNCallKeepCheckReachability';
1617
const isIOS = Platform.OS === 'ios';
1718

1819
const didReceiveStartCallAction = handler => {
19-
eventEmitter.addListener(RNCallKeepDidReceiveStartCallAction, (data) => handler(data));
20-
2120
if (isIOS) {
2221
// Tell CallKeep that we are ready to receive `RNCallKeepDidReceiveStartCallAction` event and prevent delay
2322
RNCallKeepModule._startCallActionEventListenerAdded();
2423
}
24+
25+
return eventEmitter.addListener(RNCallKeepDidReceiveStartCallAction, (data) => handler(data));
2526
};
2627

2728
const answerCall = handler =>
@@ -51,6 +52,9 @@ const didPerformDTMFAction = handler =>
5152
const didResetProvider = handler =>
5253
eventEmitter.addListener(RNCallKeepProviderReset, handler);
5354

55+
const checkReachability = handler =>
56+
eventEmitter.addListener(RNCallKeepCheckReachability, handler);
57+
5458
export const listeners = {
5559
didReceiveStartCallAction,
5660
answerCall,
@@ -62,5 +66,5 @@ export const listeners = {
6266
didToggleHoldCallAction,
6367
didPerformDTMFAction,
6468
didResetProvider,
69+
checkReachability,
6570
};
66-

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
8585
public static final String ACTION_UNHOLD_CALL = "ACTION_UNHOLD_CALL";
8686
public static final String ACTION_ONGOING_CALL = "ACTION_ONGOING_CALL";
8787
public static final String ACTION_AUDIO_SESSION = "ACTION_AUDIO_SESSION";
88+
public static final String ACTION_CHECK_REACHABILITY = "ACTION_CHECK_REACHABILITY";
8889

8990
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
9091
private static final String REACT_NATIVE_MODULE_NAME = "RNCallKeep";
@@ -360,6 +361,11 @@ public void setAvailable(Boolean active) {
360361
VoiceConnectionService.setAvailable(active);
361362
}
362363

364+
@ReactMethod
365+
public void setReachable() {
366+
VoiceConnectionService.setReachable();
367+
}
368+
363369
@ReactMethod
364370
public void setCurrentCallActive(String uuid) {
365371
Connection conn = VoiceConnectionService.getConnection(uuid);
@@ -487,6 +493,7 @@ private void registerReceiver() {
487493
intentFilter.addAction(ACTION_HOLD_CALL);
488494
intentFilter.addAction(ACTION_ONGOING_CALL);
489495
intentFilter.addAction(ACTION_AUDIO_SESSION);
496+
intentFilter.addAction(ACTION_CHECK_REACHABILITY);
490497
LocalBroadcastManager.getInstance(this.reactContext).registerReceiver(voiceBroadcastReceiver, intentFilter);
491498
isReceiverRegistered = true;
492499
}
@@ -514,9 +521,6 @@ private class VoiceBroadcastReceiver extends BroadcastReceiver {
514521
public void onReceive(Context context, Intent intent) {
515522
WritableMap args = Arguments.createMap();
516523
HashMap<String, String> attributeMap = (HashMap<String, String>)intent.getSerializableExtra("attributeMap");
517-
if (attributeMap == null) {
518-
return;
519-
}
520524

521525
switch (intent.getAction()) {
522526
case ACTION_END_CALL:
@@ -561,6 +565,9 @@ public void onReceive(Context context, Intent intent) {
561565
case ACTION_AUDIO_SESSION:
562566
sendEventToJS("RNCallKeepDidActivateAudioSession", null);
563567
break;
568+
case ACTION_CHECK_REACHABILITY:
569+
sendEventToJS("RNCallKeepCheckReachability", null);
570+
break;
564571
}
565572
}
566573
}

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

+89-21
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import android.telecom.PhoneAccountHandle;
3737
import android.telecom.TelecomManager;
3838
import android.util.Log;
39+
import android.os.SystemClock;
3940

4041
import android.app.ActivityManager;
4142
import android.app.ActivityManager.RunningTaskInfo;
@@ -62,6 +63,7 @@
6263
import static io.wazo.callkeep.RNCallKeepModule.ACTION_ONGOING_CALL;
6364
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL;
6465
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNMUTE_CALL;
66+
import static io.wazo.callkeep.RNCallKeepModule.ACTION_CHECK_REACHABILITY;
6567
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALLER_NAME;
6668
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALL_NUMBER;
6769
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALL_UUID;
@@ -70,10 +72,16 @@
7072
// @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionService.java
7173
@TargetApi(Build.VERSION_CODES.M)
7274
public class VoiceConnectionService extends ConnectionService {
73-
private static Boolean isAvailable = false;
75+
private static Boolean isAvailable;
76+
private static Boolean isInitialized;
77+
private static Boolean isReachable;
78+
private static Boolean isCheckingReachability;
79+
private static String notReachableCallUuid;
80+
private static ConnectionRequest currentConnectionRequest;
7481
private static String TAG = "RNCK:VoiceConnectionService";
7582
public static Map<String, VoiceConnection> currentConnections = new HashMap<>();
7683
public static Boolean hasOutgoingCall = false;
84+
public static VoiceConnectionService currentConnectionService = null;
7785

7886
public static Connection getConnection(String connectionId) {
7987
if (currentConnections.containsKey(connectionId)) {
@@ -85,12 +93,28 @@ public static Connection getConnection(String connectionId) {
8593
public VoiceConnectionService() {
8694
super();
8795
Log.e(TAG, "Constructor");
96+
isReachable = false;
97+
isInitialized = false;
98+
isAvailable = false;
99+
currentConnectionRequest = null;
100+
isCheckingReachability = false;
101+
currentConnectionService = this;
88102
}
89103

90104
public static void setAvailable(Boolean value) {
105+
Log.d(TAG, "setAvailable: " + (value ? "true" : "false"));
106+
if (value) {
107+
isInitialized = true;
108+
}
109+
91110
isAvailable = value;
92111
}
93112

113+
public static void setReachable() {
114+
Log.d(TAG, "setReachable");
115+
isReachable = true;
116+
VoiceConnectionService.currentConnectionRequest = null;
117+
}
94118

95119
public static void deinitConnection(String connectionId) {
96120
Log.d(TAG, "deinitConnection:" + connectionId);
@@ -116,44 +140,44 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage
116140
@Override
117141
public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
118142
VoiceConnectionService.hasOutgoingCall = true;
143+
String uuid = UUID.randomUUID().toString();
144+
145+
if (!isInitialized && !isReachable) {
146+
this.notReachableCallUuid = uuid;
147+
this.currentConnectionRequest = request;
148+
this.checkReachability();
149+
}
150+
151+
return this.makeOutgoingCall(request, uuid, false);
152+
}
119153

154+
private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Boolean forceWakeUp) {
120155
Bundle extras = request.getExtras();
121156
Connection outgoingCallConnection = null;
122157
String number = request.getAddress().getSchemeSpecificPart();
123158
String extrasNumber = extras.getString(EXTRA_CALL_NUMBER);
124159
String displayName = extras.getString(EXTRA_CALLER_NAME);
125-
String uuid = UUID.randomUUID().toString();
160+
Boolean isForeground = VoiceConnectionService.isRunning(this.getApplicationContext());
126161

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

129164
// Wakeup application if needed
130-
if (!VoiceConnectionService.isRunning(this.getApplicationContext())) {
165+
if (!isForeground || forceWakeUp) {
131166
Log.d(TAG, "onCreateOutgoingConnection: Waking up application");
132-
Intent headlessIntent = new Intent(
133-
this.getApplicationContext(),
134-
RNCallKeepBackgroundMessagingService.class
135-
);
136-
headlessIntent.putExtra("callUUID", uuid);
137-
headlessIntent.putExtra("name", displayName);
138-
headlessIntent.putExtra("handle", number);
139-
ComponentName name = this.getApplicationContext().startService(headlessIntent);
140-
if (name != null) {
141-
HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext());
142-
}
143-
} else if (!this.canMakeOutgoingCall()) {
167+
this.wakeUpApplication(uuid, number, displayName);
168+
} else if (!this.canMakeOutgoingCall() && isReachable) {
169+
Log.d(TAG, "onCreateOutgoingConnection: not available");
144170
return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.LOCAL));
145171
}
146172

147173
// TODO: Hold all other calls
148-
if (extrasNumber != null && extrasNumber.equals(number)) {
149-
outgoingCallConnection = createConnection(request);
150-
} else {
174+
if (extrasNumber == null || !extrasNumber.equals(number)) {
151175
extras.putString(EXTRA_CALL_UUID, uuid);
152176
extras.putString(EXTRA_CALLER_NAME, displayName);
153177
extras.putString(EXTRA_CALL_NUMBER, number);
154-
outgoingCallConnection = createConnection(request);
155178
}
156179

180+
outgoingCallConnection = createConnection(request);
157181
outgoingCallConnection.setDialing();
158182
outgoingCallConnection.setAudioModeIsVoip(true);
159183
outgoingCallConnection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED);
@@ -164,15 +188,59 @@ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManage
164188
sendCallRequestToActivity(ACTION_ONGOING_CALL, extrasMap);
165189
sendCallRequestToActivity(ACTION_AUDIO_SESSION, null);
166190

191+
Log.d(TAG, "onCreateOutgoingConnection: calling");
192+
167193
return outgoingCallConnection;
168194
}
169195

196+
private void wakeUpApplication(String uuid, String number, String displayName) {
197+
Intent headlessIntent = new Intent(
198+
this.getApplicationContext(),
199+
RNCallKeepBackgroundMessagingService.class
200+
);
201+
headlessIntent.putExtra("callUUID", uuid);
202+
headlessIntent.putExtra("name", displayName);
203+
headlessIntent.putExtra("handle", number);
204+
Log.d(TAG, "wakeUpApplication: " + uuid + ", number : " + number + ", displayName:" + displayName);
205+
206+
ComponentName name = this.getApplicationContext().startService(headlessIntent);
207+
if (name != null) {
208+
HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext());
209+
}
210+
}
211+
212+
private void wakeUpAfterReachabilityTimeout(ConnectionRequest request) {
213+
if (this.currentConnectionRequest == null) {
214+
return;
215+
}
216+
Log.d(TAG, "checkReachability timeout, force wakeup");
217+
Bundle extras = request.getExtras();
218+
String number = request.getAddress().getSchemeSpecificPart();
219+
String displayName = extras.getString(EXTRA_CALLER_NAME);
220+
wakeUpApplication(this.notReachableCallUuid, number, displayName);
221+
222+
VoiceConnectionService.currentConnectionRequest = null;
223+
}
224+
225+
private void checkReachability() {
226+
Log.d(TAG, "checkReachability");
227+
228+
final VoiceConnectionService instance = this;
229+
sendCallRequestToActivity(ACTION_CHECK_REACHABILITY, null);
230+
231+
new android.os.Handler().postDelayed(
232+
new Runnable() {
233+
public void run() {
234+
instance.wakeUpAfterReachabilityTimeout(instance.currentConnectionRequest);
235+
}
236+
}, 2000);
237+
}
238+
170239
private Boolean canMakeOutgoingCall() {
171240
return isAvailable;
172241
}
173242

174243
private Connection createConnection(ConnectionRequest request) {
175-
176244
Bundle extras = request.getExtras();
177245
HashMap<String, String> extrasMap = this.bundleToMap(extras);
178246
extrasMap.put(EXTRA_CALL_NUMBER, request.getAddress().toString());

index.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type Events =
88
'didToggleHoldCallAction' |
99
'didPerformDTMFAction' |
1010
'didResetProvider' |
11+
'checkReachability' |
1112
'didPerformSetMutedCallAction';
1213

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

111112
}
112113

114+
static setReachable() {
115+
116+
}
117+
113118
/**
114119
* @description supportConnectionService method is available only on Android.
115120
*/

index.js

+10-14
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,23 @@ const supportConnectionService = !isIOS && Platform.Version >= 23;
99
class RNCallKeep {
1010

1111
constructor() {
12-
this._callkitEventHandlers = new Map();
12+
this._callkeepEventHandlers = new Map();
1313
}
1414

15-
1615
addEventListener = (type, handler) => {
1716
const listener = listeners[type](handler);
1817

19-
this._callkitEventHandlers.set(handler, listener);
18+
this._callkeepEventHandlers.set(type, listener);
2019
};
2120

22-
removeEventListener = (type, handler) => {
23-
const listener = this._callkitEventHandlers.get(handler);
21+
removeEventListener = (type) => {
22+
const listener = this._callkeepEventHandlers.get(type);
2423
if (!listener) {
2524
return;
2625
}
2726

2827
listener.remove();
29-
this._callkitEventHandlers.delete(handler);
28+
this._callkeepEventHandlers.delete(type);
3029
};
3130

3231
setup = async (options) => {
@@ -83,8 +82,7 @@ class RNCallKeep {
8382
}
8483
};
8584

86-
reportEndCallWithUUID = (uuid, reason) =>
87-
RNCallKeepModule.reportEndCallWithUUID(uuid, reason);
85+
reportEndCallWithUUID = (uuid, reason) => RNCallKeepModule.reportEndCallWithUUID(uuid, reason);
8886

8987
/*
9088
* Android explicitly states we reject a call
@@ -143,18 +141,16 @@ class RNCallKeep {
143141
RNCallKeepModule.setCurrentCallActive(callUUID);
144142
};
145143

146-
updateDisplay = (uuid, displayName, handle) => RNCallKeepModule.updateDisplay(uuid, displayName, handle);
144+
updateDisplay = (uuid, displayName, uri) => RNCallKeepModule.updateDisplay(uuid, displayName, uri);
147145

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

150-
// @deprecated
151-
reportUpdatedCall = (uuid, localizedCallerName) => {
152-
console.warn('RNCallKeep.reportUpdatedCall is deprecarted, use RNCallKeep.updateDisplay instead');
148+
setReachable = () => RNCallKeepModule.setReachable();
153149

154-
return isIOS
150+
reportUpdatedCall = (uuid, localizedCallerName) =>
151+
isIOS
155152
? RNCallKeepModule.reportUpdatedCall(uuid, localizedCallerName)
156153
: Promise.reject('RNCallKeep.reportUpdatedCall was called from unsupported OS');
157-
};
158154

159155
_setupIOS = async (options) => new Promise((resolve, reject) => {
160156
if (!options.appName) {

0 commit comments

Comments
 (0)