Skip to content

Commit 71ae82e

Browse files
author
saif
committed
Some bugs fixing and new code
1 parent 23d5810 commit 71ae82e

File tree

4 files changed

+186
-12
lines changed

4 files changed

+186
-12
lines changed

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

Lines changed: 166 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
package io.wazo.callkeep;
19-
19+
import com.facebook.react.bridge.LifecycleEventListener;
2020
import android.Manifest;
2121
import android.app.Activity;
2222
import android.content.BroadcastReceiver;
@@ -48,6 +48,8 @@
4848
import android.telecom.PhoneAccountHandle;
4949
import android.telecom.TelecomManager;
5050
import android.telephony.TelephonyManager;
51+
import android.telephony.TelephonyCallback;
52+
import android.telephony.PhoneStateListener;
5153
import android.util.Log;
5254

5355
import com.facebook.react.bridge.Arguments;
@@ -101,7 +103,7 @@
101103
import static io.wazo.callkeep.Constants.ACTION_DID_CHANGE_AUDIO_ROUTE;
102104

103105
// @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionServiceActivity.java
104-
public class RNCallKeepModule extends ReactContextBaseJavaModule {
106+
public class RNCallKeepModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
105107
public static final int REQUEST_READ_PHONE_STATE = 1337;
106108
public static final int REQUEST_REGISTER_CALL_PROVIDER = 394859;
107109

@@ -117,6 +119,8 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
117119

118120
private static final String TAG = "RNCallKeep";
119121
private static TelecomManager telecomManager;
122+
private LegacyCallStateListener legacyCallStateListener;
123+
private CallStateListener callStateListener;
120124
private static TelephonyManager telephonyManager;
121125
private static Promise hasPhoneAccountPromise;
122126
private ReactApplicationContext reactContext;
@@ -126,6 +130,7 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
126130
private static WritableMap _settings;
127131
private WritableNativeArray delayedEvents;
128132
private boolean hasListeners = false;
133+
private boolean hasActiveCall = false;
129134

130135
public static RNCallKeepModule getInstance(ReactApplicationContext reactContext, boolean realContext) {
131136
if (instance == null) {
@@ -150,6 +155,8 @@ public static WritableMap getSettings(@Nullable Context context) {
150155

151156
private RNCallKeepModule(ReactApplicationContext reactContext) {
152157
super(reactContext);
158+
// This line for listening to the Activity Lifecycle Events so we can end the calls onDestroy
159+
reactContext.addLifecycleEventListener(this);
153160
Log.d(TAG, "[RNCallKeepModule] constructor");
154161

155162
this.reactContext = reactContext;
@@ -217,6 +224,125 @@ public void initializeTelecomManager() {
217224
telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
218225
}
219226

227+
228+
229+
/**
230+
* Monitors and logs phone call activities, and shows the phone state
231+
*/
232+
private class LegacyCallStateListener extends PhoneStateListener {
233+
234+
@Override
235+
public void onCallStateChanged(int state, String incomingNumber) {
236+
switch (state) {
237+
case TelephonyManager.CALL_STATE_RINGING:
238+
// Incoming call is ringing (not used for outgoing call).
239+
Log.i("onCallStateChanged", "CALL_STATE_RINGING");
240+
break;
241+
case TelephonyManager.CALL_STATE_OFFHOOK:
242+
// Phone call is active -- off the hook.
243+
// Check if there is active call in native
244+
boolean isInManagedCall = RNCallKeepModule.this.checkIsInManagedCall();
245+
246+
// Only let the JS side know if there is active app call & active native call
247+
if(RNCallKeepModule.this.hasActiveCall && isInManagedCall){
248+
WritableMap args = Arguments.createMap();
249+
RNCallKeepModule.this.sendEventToJS("RNCallKeepHasActiveCall",args);
250+
}else if(VoiceConnectionService.currentConnections.size() > 0){
251+
// Will enter here for the first time to mark the app has active call
252+
RNCallKeepModule.this.hasActiveCall = true;
253+
}
254+
Log.i("onCallStateChanged", "CALL_STATE_OFFHOOK");
255+
break;
256+
case TelephonyManager.CALL_STATE_IDLE:
257+
// Phone is idle before and after phone call.
258+
// If running on version older than 19 (KitKat),
259+
// restart activity when phone call ends.
260+
Log.i("onCallStateChanged", "CALL_STATE_IDLE");
261+
break;
262+
default:
263+
Log.i("onCallStateChanged", "default");
264+
break;
265+
}
266+
}
267+
}
268+
269+
private class CallStateListener extends TelephonyCallback implements TelephonyCallback.CallStateListener {
270+
271+
@Override
272+
public void onCallStateChanged(int state) {
273+
switch (state) {
274+
case TelephonyManager.CALL_STATE_RINGING:
275+
// Incoming call is ringing (not used for outgoing call).
276+
Log.i("onCallStateChanged", "CALL_STATE_RINGING");
277+
break;
278+
case TelephonyManager.CALL_STATE_OFFHOOK:
279+
// Phone call is active -- off the hook.
280+
281+
// Check if there is active call in native
282+
boolean isInManagedCall = RNCallKeepModule.this.checkIsInManagedCall();
283+
284+
// Only let the JS side know if there is active app call & active native call
285+
if(RNCallKeepModule.this.hasActiveCall && isInManagedCall){
286+
WritableMap args = Arguments.createMap();
287+
RNCallKeepModule.this.sendEventToJS("RNCallKeepHasActiveCall",args);
288+
}else if(VoiceConnectionService.currentConnections.size() > 0){
289+
// Will enter here for the first time to mark the app has active call
290+
RNCallKeepModule.this.hasActiveCall = true;
291+
}
292+
Log.i("onCallStateChanged", "CALL_STATE_OFFHOOK");
293+
break;
294+
case TelephonyManager.CALL_STATE_IDLE:
295+
// Phone is idle before and after phone call.
296+
// If running on version older than 19 (KitKat),
297+
// restart activity when phone call ends.
298+
Log.i("onCallStateChanged", "CALL_STATE_IDLE");
299+
break;
300+
default:
301+
Log.i("onCallStateChanged", "default");
302+
break;
303+
}
304+
}
305+
}
306+
307+
public void stopListenToNativeCallsState() {
308+
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && callStateListener !=null){
309+
Log.d(TAG, "[RNCallKeepModule] stopListenToNativeCallsState");
310+
telephonyManager.unregisterTelephonyCallback(callStateListener);
311+
}else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S && legacyCallStateListener != null){
312+
telephonyManager.listen(legacyCallStateListener, PhoneStateListener.LISTEN_NONE);
313+
}
314+
}
315+
316+
public void listenToNativeCallsState() {
317+
Context context = this.getAppContext();
318+
int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE);
319+
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
320+
Log.d(TAG, "[RNCallKeepModule] listenToNativeCallsState");
321+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
322+
callStateListener = new CallStateListener();
323+
telephonyManager.registerTelephonyCallback(context.getMainExecutor(),callStateListener);
324+
} else {
325+
legacyCallStateListener = new LegacyCallStateListener();
326+
telephonyManager.listen(legacyCallStateListener, PhoneStateListener.LISTEN_CALL_STATE);
327+
}
328+
}
329+
}
330+
331+
public boolean checkIsInManagedCall() {
332+
Context context = this.getAppContext();
333+
int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE);
334+
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
335+
return telecomManager.isInManagedCall();
336+
}
337+
return false;
338+
}
339+
340+
@ReactMethod
341+
public void checkIsInManagedCall(Promise promise) {
342+
boolean isInManagedCall = this.checkIsInManagedCall();
343+
promise.resolve(isInManagedCall);
344+
}
345+
220346
@ReactMethod
221347
public void setSettings(ReadableMap options) {
222348
Log.d(TAG, "[RNCallKeepModule] setSettings : " + options);
@@ -335,7 +461,7 @@ public void displayIncomingCall(String uuid, String number, String callerName, b
335461
if (payload != null) {
336462
extras.putBundle(EXTRA_PAYLOAD, payload);
337463
}
338-
464+
this.listenToNativeCallsState();
339465
telecomManager.addNewIncomingCall(handle, extras);
340466
}
341467

@@ -390,7 +516,7 @@ public void startCall(String uuid, String number, String callerName, boolean has
390516
extras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras);
391517

392518
Log.d(TAG, "[RNCallKeepModule] startCall, uuid: " + uuid);
393-
519+
this.listenToNativeCallsState();
394520
telecomManager.placeCall(uri, extras);
395521
}
396522

@@ -411,7 +537,8 @@ public void endCall(String uuid) {
411537
AudioManager audioManager = (AudioManager) context.getSystemService(context.AUDIO_SERVICE);
412538
audioManager.setMode(0);
413539
conn.onDisconnect();
414-
540+
this.stopListenToNativeCallsState();
541+
this.hasActiveCall = false;
415542
Log.d(TAG, "[RNCallKeepModule] endCall executed, uuid: " + uuid);
416543
}
417544

@@ -429,7 +556,8 @@ public void endAllCalls() {
429556
Connection connectionToEnd = connectionEntry.getValue();
430557
connectionToEnd.onDisconnect();
431558
}
432-
559+
this.stopListenToNativeCallsState();
560+
this.hasActiveCall = false;
433561
Log.d(TAG, "[RNCallKeepModule] endAllCalls executed");
434562
}
435563

@@ -597,6 +725,37 @@ public void reportEndCallWithUUID(String uuid, int reason) {
597725
conn.reportDisconnect(reason);
598726
}
599727

728+
@Override
729+
public void onHostResume() {
730+
Log.d(TAG, "onResume()");
731+
}
732+
733+
@Override
734+
public void onHostPause() {
735+
Log.d(TAG, "onPause()");
736+
}
737+
738+
@Override
739+
public void onHostDestroy() {
740+
// when activity destroyed end all calls
741+
Log.d(TAG, "onDestroy()");
742+
if (!isConnectionServiceAvailable() || !hasPhoneAccount()) {
743+
Log.w(TAG, "[RNCallKeepModule] endAllCalls ignored due to no ConnectionService or no phone account");
744+
return;
745+
}
746+
747+
ArrayList<Map.Entry<String, VoiceConnection>> connections =
748+
new ArrayList<Map.Entry<String, VoiceConnection>>(VoiceConnectionService.currentConnections.entrySet());
749+
for (Map.Entry<String, VoiceConnection> connectionEntry : connections) {
750+
Connection connectionToEnd = connectionEntry.getValue();
751+
connectionToEnd.onDisconnect();
752+
}
753+
this.stopListenToNativeCallsState();
754+
Log.d(TAG, "[RNCallKeepModule] endAllCalls executed");
755+
// this line will kill the android process after ending all calls
756+
android.os.Process.killProcess(android.os.Process.myPid());
757+
}
758+
600759
@ReactMethod
601760
public void rejectCall(String uuid) {
602761
Log.d(TAG, "[RNCallKeepModule] rejectCall, uuid: " + uuid);
@@ -610,7 +769,7 @@ public void rejectCall(String uuid) {
610769
Log.w(TAG, "[RNCallKeepModule] rejectCall ignored because no connection found, uuid: " + uuid);
611770
return;
612771
}
613-
772+
this.stopListenToNativeCallsState();
614773
conn.onReject();
615774
}
616775

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,23 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage
218218
@Override
219219
public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
220220
VoiceConnectionService.hasOutgoingCall = true;
221-
String uuid = UUID.randomUUID().toString();
222221

223-
Log.d(TAG, "[VoiceConnectionService] onCreateOutgoingConnection, uuid:" + uuid);
222+
Bundle extras = request.getExtras();
223+
String callUUID = extras.getString(EXTRA_CALL_UUID);
224+
225+
if(callUUID == null || callUUID == ""){
226+
callUUID = UUID.randomUUID().toString();
227+
}
228+
229+
Log.d(TAG, "[VoiceConnectionService] onCreateOutgoingConnection, uuid:" + callUUID);
224230

225231
if (!isInitialized && !isReachable) {
226-
this.notReachableCallUuid = uuid;
232+
this.notReachableCallUuid = callUUID;
227233
this.currentConnectionRequest = request;
228234
this.checkReachability();
229235
}
230236

231-
return this.makeOutgoingCall(request, uuid, false);
237+
return this.makeOutgoingCall(request, callUUID, false);
232238
}
233239

234240
private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Boolean forceWakeUp) {

index.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ declare module 'react-native-callkeep' {
1616
checkReachability: 'RNCallKeepCheckReachability';
1717
didResetProvider: 'RNCallKeepProviderReset';
1818
didLoadWithEvents: 'RNCallKeepDidLoadWithEvents';
19+
onHasActiveCall : 'onHasActiveCall';
1920
}
2021

2122
export type InitialEvents = Array<{
@@ -54,6 +55,7 @@ declare module 'react-native-callkeep' {
5455
checkReachability: undefined;
5556
didResetProvider: undefined;
5657
didLoadWithEvents: InitialEvents;
58+
onHasActiveCall : undefined;
5759
}
5860

5961
type HandleType = 'generic' | 'number' | 'email';
@@ -74,7 +76,7 @@ declare module 'react-native-callkeep' {
7476
defaultToSpeaker = 0x8,
7577
overrideMutedMicrophoneInterruption = 0x80,
7678
}
77-
79+
7880
export enum AudioSessionMode {
7981
default = 'AVAudioSessionModeDefault',
8082
gameChat = 'AVAudioSessionModeGameChat',
@@ -273,5 +275,10 @@ declare module 'react-native-callkeep' {
273275
static setCurrentCallActive(callUUID: string): void
274276

275277
static backToForeground(): void
278+
279+
/**
280+
* @descriptions Android Only, Check if there is active native call
281+
*/
282+
static checkIsInManagedCall(): Promise<boolean>
276283
}
277284
}

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ class RNCallKeep {
161161
);
162162
};
163163

164+
checkIsInManagedCall = async () => isIOS? false: RNCallKeepModule.checkIsInManagedCall();
165+
164166
answerIncomingCall = (uuid) => {
165167
RNCallKeepModule.answerIncomingCall(uuid);
166168
};

0 commit comments

Comments
 (0)