From 5074aea0861d0a391f2fd689e0a7f1791d3d724f Mon Sep 17 00:00:00 2001 From: Geraint White Date: Tue, 22 Jan 2019 09:06:34 +0000 Subject: [PATCH 1/5] Add ability to start calls on Android --- .../io/wazo/callkeep/RNCallKeepModule.java | 41 +++++++++++++++---- .../wazo/callkeep/VoiceConnectionService.java | 7 ++-- index.js | 2 +- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java index 3db792cd..753660c2 100644 --- a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java +++ b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java @@ -73,6 +73,7 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule { public static final String ACTION_HOLD_CALL = "ACTION_HOLD_CALL"; 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"; private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"; private static final String REACT_NATIVE_MODULE_NAME = "RNCallKeep"; @@ -128,6 +129,21 @@ public void displayIncomingCall(String number, String callerName) { telecomManager.addNewIncomingCall(this.pah, extras); } + @ReactMethod + public void startCall(String number, String callerName) { + if (!this.hasPhoneAccount()) { + return; + } + + Bundle extras = new Bundle(); + Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); + + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, this.pah); + extras.putString(EXTRA_CALLER_NAME, callerName); + + telecomManager.placeCall(uri, extras); + } + @ReactMethod public void endCall() { if (!hasPhoneAccount()) { @@ -156,7 +172,8 @@ public void checkPhoneAccountPermission(Promise promise) { } hasPhoneAccountPromise = promise; - if (!this.checkPermission(Manifest.permission.READ_PHONE_STATE, REQUEST_READ_PHONE_STATE)) { + String[] permissions = { Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE }; + if (!this.checkPermissions(permissions, REQUEST_READ_PHONE_STATE)) { return; } @@ -227,16 +244,22 @@ private String getApplicationName(Context appContext) { return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : appContext.getString(stringId); } - private Boolean checkPermission(String name, int id) { + private Boolean checkPermissions(String[] permissions, int id) { Activity currentActivity = this.getCurrentActivity(); - int permissionCheck = ContextCompat.checkSelfPermission(currentActivity, name); - if (permissionCheck != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(currentActivity, new String[]{name}, id); - return false; + boolean hasPermissions = true; + for (String permission : permissions) { + int permissionCheck = ContextCompat.checkSelfPermission(currentActivity, permission); + if (permissionCheck != PackageManager.PERMISSION_GRANTED) { + hasPermissions = false; + } + } + + if (!hasPermissions) { + ActivityCompat.requestPermissions(currentActivity, permissions, id); } - return true; + return hasPermissions; } private static boolean hasPhoneAccount() { @@ -266,6 +289,7 @@ private void registerReceiver() { intentFilter.addAction(ACTION_UNHOLD_CALL); intentFilter.addAction(ACTION_HOLD_CALL); intentFilter.addAction(ACTION_ONGOING_CALL); + intentFilter.addAction(ACTION_AUDIO_SESSION); LocalBroadcastManager.getInstance(this.reactContext).registerReceiver(voiceBroadcastReceiver, intentFilter); isReceiverRegistered = true; } @@ -313,6 +337,9 @@ public void onReceive(Context context, Intent intent) { sendEventToJS("RNCallKeepDidReceiveStartCallAction", args); break; + case ACTION_AUDIO_SESSION: + sendEventToJS("RNCallKeepDidActivateAudioSession", null); + break; } } } diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java index dd5036a3..d15ae553 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java @@ -40,6 +40,7 @@ import static io.wazo.callkeep.RNCallKeepModule.ACTION_HOLD_CALL; import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL; import static io.wazo.callkeep.RNCallKeepModule.ACTION_ONGOING_CALL; +import static io.wazo.callkeep.RNCallKeepModule.ACTION_AUDIO_SESSION; import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALLER_NAME; // @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionService.java @@ -78,6 +79,7 @@ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManage outgoingCallConnection.setDialing(); sendCallRequestToActivity(ACTION_ONGOING_CALL, request.getAddress().getSchemeSpecificPart()); + sendCallRequestToActivity(ACTION_AUDIO_SESSION, null); return outgoingCallConnection; } @@ -112,6 +114,7 @@ public void onAnswer() { connection.setAudioModeIsVoip(true); sendCallRequestToActivity(ACTION_ANSWER_CALL, null); + sendCallRequestToActivity(ACTION_AUDIO_SESSION, null); } @Override @@ -176,9 +179,7 @@ public void onReject() { Bundle extra = request.getExtras(); - connection.setConnectionCapabilities(Connection.CAPABILITY_MUTE); - connection.setConnectionCapabilities(Connection.CAPABILITY_HOLD); - connection.setConnectionCapabilities(Connection.CAPABILITY_SUPPORT_HOLD); + connection.setConnectionCapabilities(Connection.CAPABILITY_MUTE | Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD); connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED); connection.setExtras(extra); connection.setCallerDisplayName(extra.getString(EXTRA_CALLER_NAME), TelecomManager.PRESENTATION_ALLOWED); diff --git a/index.js b/index.js index a60a4843..401e61d3 100644 --- a/index.js +++ b/index.js @@ -50,7 +50,7 @@ class RNCallKeep { startCall(uuid, handle, handleType = 'number', hasVideo = false, contactIdentifier) { if (!isIOS) { - // Can't start a call directly on Android + RNCallKeepModule.startCall(handle, contactIdentifier); return; } From 087fcd5357e07d4e71b296816c9ad7046776d759 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Tue, 22 Jan 2019 16:14:09 +0000 Subject: [PATCH 2/5] Add Android version checks and updated permissions --- android/build.gradle | 17 ++++- android/src/main/AndroidManifest.xml | 2 + .../io/wazo/callkeep/RNCallKeepModule.java | 69 +++++++------------ .../wazo/callkeep/VoiceConnectionService.java | 19 ++--- docs/android-installation.md | 6 +- 5 files changed, 58 insertions(+), 55 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b21d495a..9a72395d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,14 @@ +buildscript { + repositories { + jcenter() + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + apply plugin: 'com.android.library' def safeExtGet(prop, fallback) { @@ -5,7 +16,7 @@ def safeExtGet(prop, fallback) { } android { - compileSdkVersion safeExtGet('compileSdkVersion', 23) + compileSdkVersion safeExtGet('compileSdkVersion', 26) buildToolsVersion safeExtGet('buildToolsVersion', "23.0.1") defaultConfig { @@ -16,6 +27,10 @@ android { } } +repositories { + mavenCentral() +} + dependencies { compile 'com.facebook.react:react-native:+' } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index cc3f0931..e8578812 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1,5 @@ + + diff --git a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java index 753660c2..95dc0b69 100644 --- a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java +++ b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java @@ -17,47 +17,36 @@ package io.wazo.callkeep; +import android.Manifest; +import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.IntentFilter; -import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; -import android.support.annotation.Nullable; - -import android.accounts.AccountManager; -import android.accounts.Account; -import android.telecom.DisconnectCause; import android.telecom.Connection; -import android.telecom.PhoneAccountHandle; +import android.telecom.DisconnectCause; import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; -import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; -import android.os.Bundle; -import android.os.Build; -import android.net.Uri; -import android.app.Activity; -import android.Manifest; - -import java.util.Map; -import java.util.HashMap; -import java.util.List; -import java.lang.SecurityException; - // @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionServiceActivity.java public class RNCallKeepModule extends ReactContextBaseJavaModule { public static final int REQUEST_READ_PHONE_STATE = 394858; @@ -81,7 +70,7 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule { private static TelecomManager telecomManager; private static Promise hasPhoneAccountPromise; private ReactApplicationContext reactContext; - private PhoneAccountHandle pah; + private static PhoneAccountHandle handle; private boolean isReceiverRegistered = false; private VoiceBroadcastReceiver voiceBroadcastReceiver; @@ -116,7 +105,7 @@ public String getName() { @ReactMethod public void displayIncomingCall(String number, String callerName) { - if (!this.hasPhoneAccount()) { + if (!isAvailable() || !hasPhoneAccount()) { return; } @@ -126,19 +115,19 @@ public void displayIncomingCall(String number, String callerName) { extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri); extras.putString(EXTRA_CALLER_NAME, callerName); - telecomManager.addNewIncomingCall(this.pah, extras); + telecomManager.addNewIncomingCall(handle, extras); } @ReactMethod public void startCall(String number, String callerName) { - if (!this.hasPhoneAccount()) { + if (!isAvailable() || !hasPhoneAccount()) { return; } Bundle extras = new Bundle(); Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); - extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, this.pah); + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle); extras.putString(EXTRA_CALLER_NAME, callerName); telecomManager.placeCall(uri, extras); @@ -146,7 +135,7 @@ public void startCall(String number, String callerName) { @ReactMethod public void endCall() { - if (!hasPhoneAccount()) { + if (!isAvailable() || !hasPhoneAccount()) { return; } @@ -218,17 +207,19 @@ public static Boolean isAvailable() { } private void registerPhoneAccount(Context appContext) { + if (!isAvailable()) { + return; + } + ComponentName cName = new ComponentName(this.getAppContext(), VoiceConnectionService.class); String appName = this.getApplicationName(appContext); - this.pah = new PhoneAccountHandle(cName, appName); + handle = new PhoneAccountHandle(cName, appName); - PhoneAccount account = new PhoneAccount.Builder(pah, appName) + PhoneAccount account = new PhoneAccount.Builder(handle, appName) .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) .build(); - PhoneAccountHandle handle = new PhoneAccountHandle(cName, appName); - telecomManager = (TelecomManager) this.getAppContext().getSystemService(this.getAppContext().TELECOM_SERVICE); telecomManager.registerPhoneAccount(account); } @@ -267,15 +258,7 @@ private static boolean hasPhoneAccount() { return false; } - List enabledAccounts = telecomManager.getCallCapablePhoneAccounts(); - - for (PhoneAccountHandle account : enabledAccounts) { - if (account.getComponentName().getClassName().equals(VoiceConnectionService.class.getCanonicalName())) { - return true; - } - } - - return false; + return telecomManager.getPhoneAccount(handle).isEnabled(); } private void registerReceiver() { diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java index d15ae553..36311580 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java @@ -17,12 +17,13 @@ package io.wazo.callkeep; +import android.annotation.TargetApi; import android.content.Intent; -import android.net.Uri; +import android.os.Build; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; +import android.os.Handler; import android.support.annotation.Nullable; - +import android.support.v4.content.LocalBroadcastManager; import android.telecom.CallAudioState; import android.telecom.Connection; import android.telecom.ConnectionRequest; @@ -30,20 +31,20 @@ import android.telecom.DisconnectCause; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; -import android.os.Handler; -import static io.wazo.callkeep.RNCallKeepModule.ACTION_END_CALL; import static io.wazo.callkeep.RNCallKeepModule.ACTION_ANSWER_CALL; -import static io.wazo.callkeep.RNCallKeepModule.ACTION_MUTE_CALL; -import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNMUTE_CALL; +import static io.wazo.callkeep.RNCallKeepModule.ACTION_AUDIO_SESSION; import static io.wazo.callkeep.RNCallKeepModule.ACTION_DTMF_TONE; +import static io.wazo.callkeep.RNCallKeepModule.ACTION_END_CALL; import static io.wazo.callkeep.RNCallKeepModule.ACTION_HOLD_CALL; -import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL; +import static io.wazo.callkeep.RNCallKeepModule.ACTION_MUTE_CALL; import static io.wazo.callkeep.RNCallKeepModule.ACTION_ONGOING_CALL; -import static io.wazo.callkeep.RNCallKeepModule.ACTION_AUDIO_SESSION; +import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL; +import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNMUTE_CALL; import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALLER_NAME; // @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 Connection connection; private static Boolean isActive = false; diff --git a/docs/android-installation.md b/docs/android-installation.md index 0d39dc68..0ff8007c 100644 --- a/docs/android-installation.md +++ b/docs/android-installation.md @@ -40,7 +40,7 @@ import io.wazo.callkeep.RNCallKeepModule; // Add this import line with others public class MainActivity extends ReactActivity { // ... - + // Permission results @Override public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) { @@ -61,8 +61,10 @@ public class MainActivity extends ReactActivity { ```xml + + - + // ... Date: Wed, 23 Jan 2019 15:52:28 +0000 Subject: [PATCH 3/5] Fix caller ID not being passed in startCall --- android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java index 95dc0b69..77aa4d33 100644 --- a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java +++ b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java @@ -127,8 +127,11 @@ public void startCall(String number, String callerName) { Bundle extras = new Bundle(); Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); + Bundle callExtras = new Bundle(); + callExtras.putString(EXTRA_CALLER_NAME, callerName); + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle); - extras.putString(EXTRA_CALLER_NAME, callerName); + extras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras); telecomManager.placeCall(uri, extras); } From 3e7edee788741722a583442a49b3e548d8bdc639 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Tue, 29 Jan 2019 14:43:25 +0000 Subject: [PATCH 4/5] Change SDK version to 23 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 9a72395d..07e88d0c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,7 +16,7 @@ def safeExtGet(prop, fallback) { } android { - compileSdkVersion safeExtGet('compileSdkVersion', 26) + compileSdkVersion safeExtGet('compileSdkVersion', 23) buildToolsVersion safeExtGet('buildToolsVersion', "23.0.1") defaultConfig { From 7cb4180c3af923f5d7745d9703c3ebdc1a45ff7b Mon Sep 17 00:00:00 2001 From: Geraint White Date: Tue, 29 Jan 2019 14:43:54 +0000 Subject: [PATCH 5/5] Set audio mode to VoIP for outgoing calls --- .../src/main/java/io/wazo/callkeep/VoiceConnectionService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java index 36311580..2201c6a2 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java @@ -78,6 +78,7 @@ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManage Connection outgoingCallConnection = createConnection(request); outgoingCallConnection.setDialing(); + outgoingCallConnection.setAudioModeIsVoip(true); sendCallRequestToActivity(ACTION_ONGOING_CALL, request.getAddress().getSchemeSpecificPart()); sendCallRequestToActivity(ACTION_AUDIO_SESSION, null);