16
16
*/
17
17
18
18
package io .wazo .callkeep ;
19
-
19
+ import com . facebook . react . bridge . LifecycleEventListener ;
20
20
import android .Manifest ;
21
21
import android .app .Activity ;
22
22
import android .content .BroadcastReceiver ;
48
48
import android .telecom .PhoneAccountHandle ;
49
49
import android .telecom .TelecomManager ;
50
50
import android .telephony .TelephonyManager ;
51
+ import android .telephony .TelephonyCallback ;
52
+ import android .telephony .PhoneStateListener ;
51
53
import android .util .Log ;
52
54
53
55
import com .facebook .react .bridge .Arguments ;
101
103
import static io .wazo .callkeep .Constants .ACTION_DID_CHANGE_AUDIO_ROUTE ;
102
104
103
105
// @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 {
105
107
public static final int REQUEST_READ_PHONE_STATE = 1337 ;
106
108
public static final int REQUEST_REGISTER_CALL_PROVIDER = 394859 ;
107
109
@@ -117,6 +119,8 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
117
119
118
120
private static final String TAG = "RNCallKeep" ;
119
121
private static TelecomManager telecomManager ;
122
+ private LegacyCallStateListener legacyCallStateListener ;
123
+ private CallStateListener callStateListener ;
120
124
private static TelephonyManager telephonyManager ;
121
125
private static Promise hasPhoneAccountPromise ;
122
126
private ReactApplicationContext reactContext ;
@@ -126,6 +130,7 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
126
130
private static WritableMap _settings ;
127
131
private WritableNativeArray delayedEvents ;
128
132
private boolean hasListeners = false ;
133
+ private boolean hasActiveCall = false ;
129
134
130
135
public static RNCallKeepModule getInstance (ReactApplicationContext reactContext , boolean realContext ) {
131
136
if (instance == null ) {
@@ -150,6 +155,8 @@ public static WritableMap getSettings(@Nullable Context context) {
150
155
151
156
private RNCallKeepModule (ReactApplicationContext reactContext ) {
152
157
super (reactContext );
158
+ // This line for listening to the Activity Lifecycle Events so we can end the calls onDestroy
159
+ reactContext .addLifecycleEventListener (this );
153
160
Log .d (TAG , "[RNCallKeepModule] constructor" );
154
161
155
162
this .reactContext = reactContext ;
@@ -217,6 +224,120 @@ public void initializeTelecomManager() {
217
224
telecomManager = (TelecomManager ) context .getSystemService (Context .TELECOM_SERVICE );
218
225
}
219
226
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
+ break ;
240
+ case TelephonyManager .CALL_STATE_OFFHOOK :
241
+ // Phone call is active -- off the hook.
242
+ // Check if there is active call in native
243
+ boolean isInManagedCall = RNCallKeepModule .this .checkIsInManagedCall ();
244
+
245
+ // Only let the JS side know if there is active app call & active native call
246
+ if (RNCallKeepModule .this .hasActiveCall && isInManagedCall ){
247
+ WritableMap args = Arguments .createMap ();
248
+ RNCallKeepModule .this .sendEventToJS ("RNCallKeepHasActiveCall" ,args );
249
+ }else if (VoiceConnectionService .currentConnections .size () > 0 ){
250
+ // Will enter here for the first time to mark the app has active call
251
+ RNCallKeepModule .this .hasActiveCall = true ;
252
+ }
253
+ break ;
254
+ case TelephonyManager .CALL_STATE_IDLE :
255
+ // Phone is idle before and after phone call.
256
+ // If running on version older than 19 (KitKat),
257
+ // restart activity when phone call ends.
258
+ break ;
259
+ default :
260
+ break ;
261
+ }
262
+ }
263
+ }
264
+
265
+ private class CallStateListener extends TelephonyCallback implements TelephonyCallback .CallStateListener {
266
+
267
+ @ Override
268
+ public void onCallStateChanged (int state ) {
269
+ switch (state ) {
270
+ case TelephonyManager .CALL_STATE_RINGING :
271
+ // Incoming call is ringing (not used for outgoing call).
272
+ break ;
273
+ case TelephonyManager .CALL_STATE_OFFHOOK :
274
+ // Phone call is active -- off the hook.
275
+
276
+ // Check if there is active call in native
277
+ boolean isInManagedCall = RNCallKeepModule .this .checkIsInManagedCall ();
278
+
279
+ // Only let the JS side know if there is active app call & active native call
280
+ if (RNCallKeepModule .this .hasActiveCall && isInManagedCall ){
281
+ WritableMap args = Arguments .createMap ();
282
+ RNCallKeepModule .this .sendEventToJS ("RNCallKeepHasActiveCall" ,args );
283
+ }else if (VoiceConnectionService .currentConnections .size () > 0 ){
284
+ // Will enter here for the first time to mark the app has active call
285
+ RNCallKeepModule .this .hasActiveCall = true ;
286
+ }
287
+ break ;
288
+ case TelephonyManager .CALL_STATE_IDLE :
289
+ // Phone is idle before and after phone call.
290
+ // If running on version older than 19 (KitKat),
291
+ // restart activity when phone call ends.
292
+ break ;
293
+ default :
294
+ break ;
295
+ }
296
+ }
297
+ }
298
+
299
+ public void stopListenToNativeCallsState () {
300
+ Log .d (TAG , "[RNCallKeepModule] stopListenToNativeCallsState" );
301
+
302
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S && callStateListener !=null ){
303
+ telephonyManager .unregisterTelephonyCallback (callStateListener );
304
+ } else if (Build .VERSION .SDK_INT < Build .VERSION_CODES .S && legacyCallStateListener != null ){
305
+ telephonyManager .listen (legacyCallStateListener , PhoneStateListener .LISTEN_NONE );
306
+ }
307
+ }
308
+
309
+ public void listenToNativeCallsState () {
310
+ Log .d (TAG , "[RNCallKeepModule] listenToNativeCallsState" );
311
+ Context context = this .getAppContext ();
312
+ int permissionCheck = ContextCompat .checkSelfPermission (context , Manifest .permission .READ_PHONE_STATE );
313
+
314
+ if (permissionCheck == PackageManager .PERMISSION_GRANTED ) {
315
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
316
+ callStateListener = new CallStateListener ();
317
+ telephonyManager .registerTelephonyCallback (context .getMainExecutor (),callStateListener );
318
+ } else {
319
+ legacyCallStateListener = new LegacyCallStateListener ();
320
+ telephonyManager .listen (legacyCallStateListener , PhoneStateListener .LISTEN_CALL_STATE );
321
+ }
322
+ }
323
+ }
324
+
325
+ public boolean checkIsInManagedCall () {
326
+ Context context = this .getAppContext ();
327
+ int permissionCheck = ContextCompat .checkSelfPermission (context , Manifest .permission .READ_PHONE_STATE );
328
+
329
+ if (permissionCheck == PackageManager .PERMISSION_GRANTED ) {
330
+ return telecomManager .isInManagedCall ();
331
+ }
332
+ return false ;
333
+ }
334
+
335
+ @ ReactMethod
336
+ public void checkIsInManagedCall (Promise promise ) {
337
+ boolean isInManagedCall = this .checkIsInManagedCall ();
338
+ promise .resolve (isInManagedCall );
339
+ }
340
+
220
341
@ ReactMethod
221
342
public void setSettings (ReadableMap options ) {
222
343
Log .d (TAG , "[RNCallKeepModule] setSettings : " + options );
@@ -335,7 +456,7 @@ public void displayIncomingCall(String uuid, String number, String callerName, b
335
456
if (payload != null ) {
336
457
extras .putBundle (EXTRA_PAYLOAD , payload );
337
458
}
338
-
459
+ this . listenToNativeCallsState ();
339
460
telecomManager .addNewIncomingCall (handle , extras );
340
461
}
341
462
@@ -390,7 +511,7 @@ public void startCall(String uuid, String number, String callerName, boolean has
390
511
extras .putParcelable (TelecomManager .EXTRA_OUTGOING_CALL_EXTRAS , callExtras );
391
512
392
513
Log .d (TAG , "[RNCallKeepModule] startCall, uuid: " + uuid );
393
-
514
+ this . listenToNativeCallsState ();
394
515
telecomManager .placeCall (uri , extras );
395
516
}
396
517
@@ -411,7 +532,8 @@ public void endCall(String uuid) {
411
532
AudioManager audioManager = (AudioManager ) context .getSystemService (context .AUDIO_SERVICE );
412
533
audioManager .setMode (0 );
413
534
conn .onDisconnect ();
414
-
535
+ this .stopListenToNativeCallsState ();
536
+ this .hasActiveCall = false ;
415
537
Log .d (TAG , "[RNCallKeepModule] endCall executed, uuid: " + uuid );
416
538
}
417
539
@@ -429,7 +551,8 @@ public void endAllCalls() {
429
551
Connection connectionToEnd = connectionEntry .getValue ();
430
552
connectionToEnd .onDisconnect ();
431
553
}
432
-
554
+ this .stopListenToNativeCallsState ();
555
+ this .hasActiveCall = false ;
433
556
Log .d (TAG , "[RNCallKeepModule] endAllCalls executed" );
434
557
}
435
558
@@ -597,6 +720,37 @@ public void reportEndCallWithUUID(String uuid, int reason) {
597
720
conn .reportDisconnect (reason );
598
721
}
599
722
723
+ @ Override
724
+ public void onHostResume () {
725
+
726
+ }
727
+
728
+ @ Override
729
+ public void onHostPause () {
730
+
731
+ }
732
+
733
+ @ Override
734
+ public void onHostDestroy () {
735
+ // When activity destroyed end all calls
736
+ Log .d (TAG , "[RNCallKeepModule] onHostDestroy called" );
737
+ if (!isConnectionServiceAvailable () || !hasPhoneAccount ()) {
738
+ Log .w (TAG , "[RNCallKeepModule] onHostDestroy ignored due to no ConnectionService or no phone account" );
739
+ return ;
740
+ }
741
+
742
+ ArrayList <Map .Entry <String , VoiceConnection >> connections =
743
+ new ArrayList <Map .Entry <String , VoiceConnection >>(VoiceConnectionService .currentConnections .entrySet ());
744
+ for (Map .Entry <String , VoiceConnection > connectionEntry : connections ) {
745
+ Connection connectionToEnd = connectionEntry .getValue ();
746
+ connectionToEnd .onDisconnect ();
747
+ }
748
+ this .stopListenToNativeCallsState ();
749
+ Log .d (TAG , "[RNCallKeepModule] onHostDestroy executed" );
750
+ // This line will kill the android process after ending all calls
751
+ android .os .Process .killProcess (android .os .Process .myPid ());
752
+ }
753
+
600
754
@ ReactMethod
601
755
public void rejectCall (String uuid ) {
602
756
Log .d (TAG , "[RNCallKeepModule] rejectCall, uuid: " + uuid );
@@ -610,7 +764,7 @@ public void rejectCall(String uuid) {
610
764
Log .w (TAG , "[RNCallKeepModule] rejectCall ignored because no connection found, uuid: " + uuid );
611
765
return ;
612
766
}
613
-
767
+ this . stopListenToNativeCallsState ();
614
768
conn .onReject ();
615
769
}
616
770
0 commit comments