Skip to content

Commit 581053a

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

File tree

6 files changed

+127
-35
lines changed

6 files changed

+127
-35
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,17 @@ RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => {
474474
- The digits that emit the dtmf tone
475475
- `callUUID` (string)
476476
- 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+
```
477488

478489
## Example
479490

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

+86-21
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import static io.wazo.callkeep.RNCallKeepModule.ACTION_ONGOING_CALL;
6363
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL;
6464
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNMUTE_CALL;
65+
import static io.wazo.callkeep.RNCallKeepModule.ACTION_CHECK_REACHABILITY;
6566
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALLER_NAME;
6667
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALL_NUMBER;
6768
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALL_UUID;
@@ -70,10 +71,15 @@
7071
// @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionService.java
7172
@TargetApi(Build.VERSION_CODES.M)
7273
public class VoiceConnectionService extends ConnectionService {
73-
private static Boolean isAvailable = false;
74+
private static Boolean isAvailable;
75+
private static Boolean isInitialized;
76+
private static Boolean isReachable;
77+
private static String notReachableCallUuid;
78+
private static ConnectionRequest currentConnectionRequest;
7479
private static String TAG = "RNCK:VoiceConnectionService";
7580
public static Map<String, VoiceConnection> currentConnections = new HashMap<>();
7681
public static Boolean hasOutgoingCall = false;
82+
public static VoiceConnectionService currentConnectionService = null;
7783

7884
public static Connection getConnection(String connectionId) {
7985
if (currentConnections.containsKey(connectionId)) {
@@ -85,12 +91,27 @@ public static Connection getConnection(String connectionId) {
8591
public VoiceConnectionService() {
8692
super();
8793
Log.e(TAG, "Constructor");
94+
isReachable = false;
95+
isInitialized = false;
96+
isAvailable = false;
97+
currentConnectionRequest = null;
98+
currentConnectionService = this;
8899
}
89100

90101
public static void setAvailable(Boolean value) {
102+
Log.d(TAG, "setAvailable: " + (value ? "true" : "false"));
103+
if (value) {
104+
isInitialized = true;
105+
}
106+
91107
isAvailable = value;
92108
}
93109

110+
public static void setReachable() {
111+
Log.d(TAG, "setReachable");
112+
isReachable = true;
113+
VoiceConnectionService.currentConnectionRequest = null;
114+
}
94115

95116
public static void deinitConnection(String connectionId) {
96117
Log.d(TAG, "deinitConnection:" + connectionId);
@@ -116,44 +137,44 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage
116137
@Override
117138
public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
118139
VoiceConnectionService.hasOutgoingCall = true;
140+
String uuid = UUID.randomUUID().toString();
141+
142+
if (!isInitialized && !isReachable) {
143+
this.notReachableCallUuid = uuid;
144+
this.currentConnectionRequest = request;
145+
this.checkReachability();
146+
}
147+
148+
return this.makeOutgoingCall(request, uuid, false);
149+
}
119150

151+
private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Boolean forceWakeUp) {
120152
Bundle extras = request.getExtras();
121153
Connection outgoingCallConnection = null;
122154
String number = request.getAddress().getSchemeSpecificPart();
123155
String extrasNumber = extras.getString(EXTRA_CALL_NUMBER);
124156
String displayName = extras.getString(EXTRA_CALLER_NAME);
125-
String uuid = UUID.randomUUID().toString();
157+
Boolean isForeground = VoiceConnectionService.isRunning(this.getApplicationContext());
126158

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

129161
// Wakeup application if needed
130-
if (!VoiceConnectionService.isRunning(this.getApplicationContext())) {
162+
if (!isForeground || forceWakeUp) {
131163
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()) {
164+
this.wakeUpApplication(uuid, number, displayName);
165+
} else if (!this.canMakeOutgoingCall() && isReachable) {
166+
Log.d(TAG, "onCreateOutgoingConnection: not available");
144167
return Connection.createFailedConnection(new DisconnectCause(DisconnectCause.LOCAL));
145168
}
146169

147170
// TODO: Hold all other calls
148-
if (extrasNumber != null && extrasNumber.equals(number)) {
149-
outgoingCallConnection = createConnection(request);
150-
} else {
171+
if (extrasNumber == null || !extrasNumber.equals(number)) {
151172
extras.putString(EXTRA_CALL_UUID, uuid);
152173
extras.putString(EXTRA_CALLER_NAME, displayName);
153174
extras.putString(EXTRA_CALL_NUMBER, number);
154-
outgoingCallConnection = createConnection(request);
155175
}
156176

177+
outgoingCallConnection = createConnection(request);
157178
outgoingCallConnection.setDialing();
158179
outgoingCallConnection.setAudioModeIsVoip(true);
159180
outgoingCallConnection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED);
@@ -164,15 +185,59 @@ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManage
164185
sendCallRequestToActivity(ACTION_ONGOING_CALL, extrasMap);
165186
sendCallRequestToActivity(ACTION_AUDIO_SESSION, null);
166187

188+
Log.d(TAG, "onCreateOutgoingConnection: calling");
189+
167190
return outgoingCallConnection;
168191
}
169192

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

174240
private Connection createConnection(ConnectionRequest request) {
175-
176241
Bundle extras = request.getExtras();
177242
HashMap<String, String> extrasMap = this.bundleToMap(extras);
178243
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

+8-8
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
@@ -147,6 +145,8 @@ class RNCallKeep {
147145

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

148+
setReachable = () => RNCallKeepModule.setReachable();
149+
150150
// @deprecated
151151
reportUpdatedCall = (uuid, localizedCallerName) => {
152152
console.warn('RNCallKeep.reportUpdatedCall is deprecarted, use RNCallKeep.updateDisplay instead');

0 commit comments

Comments
 (0)