diff --git a/android/build.gradle b/android/build.gradle
index b21d495..07e88d0 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) {
@@ -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 cc3f093..e857881 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 3db792c..77aa4d3 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;
@@ -73,6 +62,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";
@@ -80,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;
@@ -115,7 +105,7 @@ public String getName() {
@ReactMethod
public void displayIncomingCall(String number, String callerName) {
- if (!this.hasPhoneAccount()) {
+ if (!isAvailable() || !hasPhoneAccount()) {
return;
}
@@ -125,12 +115,30 @@ 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 (!isAvailable() || !hasPhoneAccount()) {
+ return;
+ }
+
+ 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.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras);
+
+ telecomManager.placeCall(uri, extras);
}
@ReactMethod
public void endCall() {
- if (!hasPhoneAccount()) {
+ if (!isAvailable() || !hasPhoneAccount()) {
return;
}
@@ -156,7 +164,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;
}
@@ -201,17 +210,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);
}
@@ -227,16 +238,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;
+ }
}
- return true;
+ if (!hasPermissions) {
+ ActivityCompat.requestPermissions(currentActivity, permissions, id);
+ }
+
+ return hasPermissions;
}
private static boolean hasPhoneAccount() {
@@ -244,15 +261,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() {
@@ -266,6 +275,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 +323,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 dd5036a..2201c6a 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,19 +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_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;
@@ -76,8 +78,10 @@ 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);
return outgoingCallConnection;
}
@@ -112,6 +116,7 @@ public void onAnswer() {
connection.setAudioModeIsVoip(true);
sendCallRequestToActivity(ACTION_ANSWER_CALL, null);
+ sendCallRequestToActivity(ACTION_AUDIO_SESSION, null);
}
@Override
@@ -176,9 +181,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/docs/android-installation.md b/docs/android-installation.md
index 0d39dc6..0ff8007 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
+
+
-
+
// ...