Skip to content

Commit 06b5c47

Browse files
authored
Merge pull request react-native-webrtc#5 from grit96/master
Add ability to start calls on Android
2 parents bdfd5de + 7cb4180 commit 06b5c47

File tree

6 files changed

+97
-62
lines changed

6 files changed

+97
-62
lines changed

android/build.gradle

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
buildscript {
2+
repositories {
3+
jcenter()
4+
google()
5+
}
6+
7+
dependencies {
8+
classpath 'com.android.tools.build:gradle:3.0.1'
9+
}
10+
}
11+
112
apply plugin: 'com.android.library'
213

314
def safeExtGet(prop, fallback) {
@@ -16,6 +27,10 @@ android {
1627
}
1728
}
1829

30+
repositories {
31+
mavenCentral()
32+
}
33+
1934
dependencies {
2035
compile 'com.facebook.react:react-native:+'
2136
}

android/src/main/AndroidManifest.xml

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wazo.callkeep">
3+
4+
<uses-permission android:name="android.permission.CALL_PHONE" />
35
</manifest>

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

+61-48
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,36 @@
1717

1818
package io.wazo.callkeep;
1919

20+
import android.Manifest;
21+
import android.app.Activity;
22+
import android.content.BroadcastReceiver;
2023
import android.content.ComponentName;
2124
import android.content.Context;
22-
import android.content.BroadcastReceiver;
25+
import android.content.Intent;
26+
import android.content.IntentFilter;
2327
import android.content.pm.ApplicationInfo;
2428
import android.content.pm.PackageManager;
25-
import android.content.IntentFilter;
26-
import android.content.Intent;
29+
import android.net.Uri;
30+
import android.os.Build;
31+
import android.os.Bundle;
32+
import android.support.annotation.Nullable;
2733
import android.support.v4.app.ActivityCompat;
2834
import android.support.v4.content.ContextCompat;
2935
import android.support.v4.content.LocalBroadcastManager;
30-
import android.support.annotation.Nullable;
31-
32-
import android.accounts.AccountManager;
33-
import android.accounts.Account;
34-
import android.telecom.DisconnectCause;
3536
import android.telecom.Connection;
36-
import android.telecom.PhoneAccountHandle;
37+
import android.telecom.DisconnectCause;
3738
import android.telecom.PhoneAccount;
39+
import android.telecom.PhoneAccountHandle;
3840
import android.telecom.TelecomManager;
3941

40-
import com.facebook.react.bridge.NativeModule;
42+
import com.facebook.react.bridge.Arguments;
43+
import com.facebook.react.bridge.Promise;
4144
import com.facebook.react.bridge.ReactApplicationContext;
42-
import com.facebook.react.bridge.ReactContext;
4345
import com.facebook.react.bridge.ReactContextBaseJavaModule;
4446
import com.facebook.react.bridge.ReactMethod;
4547
import com.facebook.react.bridge.WritableMap;
46-
import com.facebook.react.bridge.Arguments;
47-
import com.facebook.react.bridge.Promise;
4848
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
4949

50-
import android.os.Bundle;
51-
import android.os.Build;
52-
import android.net.Uri;
53-
import android.app.Activity;
54-
import android.Manifest;
55-
56-
import java.util.Map;
57-
import java.util.HashMap;
58-
import java.util.List;
59-
import java.lang.SecurityException;
60-
6150
// @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionServiceActivity.java
6251
public class RNCallKeepModule extends ReactContextBaseJavaModule {
6352
public static final int REQUEST_READ_PHONE_STATE = 394858;
@@ -73,14 +62,15 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
7362
public static final String ACTION_HOLD_CALL = "ACTION_HOLD_CALL";
7463
public static final String ACTION_UNHOLD_CALL = "ACTION_UNHOLD_CALL";
7564
public static final String ACTION_ONGOING_CALL = "ACTION_ONGOING_CALL";
65+
public static final String ACTION_AUDIO_SESSION = "ACTION_AUDIO_SESSION";
7666

7767
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
7868
private static final String REACT_NATIVE_MODULE_NAME = "RNCallKeep";
7969

8070
private static TelecomManager telecomManager;
8171
private static Promise hasPhoneAccountPromise;
8272
private ReactApplicationContext reactContext;
83-
private PhoneAccountHandle pah;
73+
private static PhoneAccountHandle handle;
8474
private boolean isReceiverRegistered = false;
8575
private VoiceBroadcastReceiver voiceBroadcastReceiver;
8676

@@ -115,7 +105,7 @@ public String getName() {
115105

116106
@ReactMethod
117107
public void displayIncomingCall(String number, String callerName) {
118-
if (!this.hasPhoneAccount()) {
108+
if (!isAvailable() || !hasPhoneAccount()) {
119109
return;
120110
}
121111

@@ -125,12 +115,30 @@ public void displayIncomingCall(String number, String callerName) {
125115
extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri);
126116
extras.putString(EXTRA_CALLER_NAME, callerName);
127117

128-
telecomManager.addNewIncomingCall(this.pah, extras);
118+
telecomManager.addNewIncomingCall(handle, extras);
119+
}
120+
121+
@ReactMethod
122+
public void startCall(String number, String callerName) {
123+
if (!isAvailable() || !hasPhoneAccount()) {
124+
return;
125+
}
126+
127+
Bundle extras = new Bundle();
128+
Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
129+
130+
Bundle callExtras = new Bundle();
131+
callExtras.putString(EXTRA_CALLER_NAME, callerName);
132+
133+
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle);
134+
extras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras);
135+
136+
telecomManager.placeCall(uri, extras);
129137
}
130138

131139
@ReactMethod
132140
public void endCall() {
133-
if (!hasPhoneAccount()) {
141+
if (!isAvailable() || !hasPhoneAccount()) {
134142
return;
135143
}
136144

@@ -156,7 +164,8 @@ public void checkPhoneAccountPermission(Promise promise) {
156164
}
157165

158166
hasPhoneAccountPromise = promise;
159-
if (!this.checkPermission(Manifest.permission.READ_PHONE_STATE, REQUEST_READ_PHONE_STATE)) {
167+
String[] permissions = { Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE };
168+
if (!this.checkPermissions(permissions, REQUEST_READ_PHONE_STATE)) {
160169
return;
161170
}
162171

@@ -201,17 +210,19 @@ public static Boolean isAvailable() {
201210
}
202211

203212
private void registerPhoneAccount(Context appContext) {
213+
if (!isAvailable()) {
214+
return;
215+
}
216+
204217
ComponentName cName = new ComponentName(this.getAppContext(), VoiceConnectionService.class);
205218
String appName = this.getApplicationName(appContext);
206219

207-
this.pah = new PhoneAccountHandle(cName, appName);
220+
handle = new PhoneAccountHandle(cName, appName);
208221

209-
PhoneAccount account = new PhoneAccount.Builder(pah, appName)
222+
PhoneAccount account = new PhoneAccount.Builder(handle, appName)
210223
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
211224
.build();
212225

213-
PhoneAccountHandle handle = new PhoneAccountHandle(cName, appName);
214-
215226
telecomManager = (TelecomManager) this.getAppContext().getSystemService(this.getAppContext().TELECOM_SERVICE);
216227
telecomManager.registerPhoneAccount(account);
217228
}
@@ -227,32 +238,30 @@ private String getApplicationName(Context appContext) {
227238
return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : appContext.getString(stringId);
228239
}
229240

230-
private Boolean checkPermission(String name, int id) {
241+
private Boolean checkPermissions(String[] permissions, int id) {
231242
Activity currentActivity = this.getCurrentActivity();
232-
int permissionCheck = ContextCompat.checkSelfPermission(currentActivity, name);
233243

234-
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
235-
ActivityCompat.requestPermissions(currentActivity, new String[]{name}, id);
236-
return false;
244+
boolean hasPermissions = true;
245+
for (String permission : permissions) {
246+
int permissionCheck = ContextCompat.checkSelfPermission(currentActivity, permission);
247+
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
248+
hasPermissions = false;
249+
}
237250
}
238251

239-
return true;
252+
if (!hasPermissions) {
253+
ActivityCompat.requestPermissions(currentActivity, permissions, id);
254+
}
255+
256+
return hasPermissions;
240257
}
241258

242259
private static boolean hasPhoneAccount() {
243260
if (!isAvailable()) {
244261
return false;
245262
}
246263

247-
List<PhoneAccountHandle> enabledAccounts = telecomManager.getCallCapablePhoneAccounts();
248-
249-
for (PhoneAccountHandle account : enabledAccounts) {
250-
if (account.getComponentName().getClassName().equals(VoiceConnectionService.class.getCanonicalName())) {
251-
return true;
252-
}
253-
}
254-
255-
return false;
264+
return telecomManager.getPhoneAccount(handle).isEnabled();
256265
}
257266

258267
private void registerReceiver() {
@@ -266,6 +275,7 @@ private void registerReceiver() {
266275
intentFilter.addAction(ACTION_UNHOLD_CALL);
267276
intentFilter.addAction(ACTION_HOLD_CALL);
268277
intentFilter.addAction(ACTION_ONGOING_CALL);
278+
intentFilter.addAction(ACTION_AUDIO_SESSION);
269279
LocalBroadcastManager.getInstance(this.reactContext).registerReceiver(voiceBroadcastReceiver, intentFilter);
270280
isReceiverRegistered = true;
271281
}
@@ -313,6 +323,9 @@ public void onReceive(Context context, Intent intent) {
313323

314324
sendEventToJS("RNCallKeepDidReceiveStartCallAction", args);
315325
break;
326+
case ACTION_AUDIO_SESSION:
327+
sendEventToJS("RNCallKeepDidActivateAudioSession", null);
328+
break;
316329
}
317330
}
318331
}

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

+14-11
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,34 @@
1717

1818
package io.wazo.callkeep;
1919

20+
import android.annotation.TargetApi;
2021
import android.content.Intent;
21-
import android.net.Uri;
22+
import android.os.Build;
2223
import android.os.Bundle;
23-
import android.support.v4.content.LocalBroadcastManager;
24+
import android.os.Handler;
2425
import android.support.annotation.Nullable;
25-
26+
import android.support.v4.content.LocalBroadcastManager;
2627
import android.telecom.CallAudioState;
2728
import android.telecom.Connection;
2829
import android.telecom.ConnectionRequest;
2930
import android.telecom.ConnectionService;
3031
import android.telecom.DisconnectCause;
3132
import android.telecom.PhoneAccountHandle;
3233
import android.telecom.TelecomManager;
33-
import android.os.Handler;
3434

35-
import static io.wazo.callkeep.RNCallKeepModule.ACTION_END_CALL;
3635
import static io.wazo.callkeep.RNCallKeepModule.ACTION_ANSWER_CALL;
37-
import static io.wazo.callkeep.RNCallKeepModule.ACTION_MUTE_CALL;
38-
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNMUTE_CALL;
36+
import static io.wazo.callkeep.RNCallKeepModule.ACTION_AUDIO_SESSION;
3937
import static io.wazo.callkeep.RNCallKeepModule.ACTION_DTMF_TONE;
38+
import static io.wazo.callkeep.RNCallKeepModule.ACTION_END_CALL;
4039
import static io.wazo.callkeep.RNCallKeepModule.ACTION_HOLD_CALL;
41-
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL;
40+
import static io.wazo.callkeep.RNCallKeepModule.ACTION_MUTE_CALL;
4241
import static io.wazo.callkeep.RNCallKeepModule.ACTION_ONGOING_CALL;
42+
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNHOLD_CALL;
43+
import static io.wazo.callkeep.RNCallKeepModule.ACTION_UNMUTE_CALL;
4344
import static io.wazo.callkeep.RNCallKeepModule.EXTRA_CALLER_NAME;
4445

4546
// @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionService.java
47+
@TargetApi(Build.VERSION_CODES.M)
4648
public class VoiceConnectionService extends ConnectionService {
4749
private static Connection connection;
4850
private static Boolean isActive = false;
@@ -76,8 +78,10 @@ public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManage
7678

7779
Connection outgoingCallConnection = createConnection(request);
7880
outgoingCallConnection.setDialing();
81+
outgoingCallConnection.setAudioModeIsVoip(true);
7982

8083
sendCallRequestToActivity(ACTION_ONGOING_CALL, request.getAddress().getSchemeSpecificPart());
84+
sendCallRequestToActivity(ACTION_AUDIO_SESSION, null);
8185

8286
return outgoingCallConnection;
8387
}
@@ -112,6 +116,7 @@ public void onAnswer() {
112116
connection.setAudioModeIsVoip(true);
113117

114118
sendCallRequestToActivity(ACTION_ANSWER_CALL, null);
119+
sendCallRequestToActivity(ACTION_AUDIO_SESSION, null);
115120
}
116121

117122
@Override
@@ -176,9 +181,7 @@ public void onReject() {
176181

177182
Bundle extra = request.getExtras();
178183

179-
connection.setConnectionCapabilities(Connection.CAPABILITY_MUTE);
180-
connection.setConnectionCapabilities(Connection.CAPABILITY_HOLD);
181-
connection.setConnectionCapabilities(Connection.CAPABILITY_SUPPORT_HOLD);
184+
connection.setConnectionCapabilities(Connection.CAPABILITY_MUTE | Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD);
182185
connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
183186
connection.setExtras(extra);
184187
connection.setCallerDisplayName(extra.getString(EXTRA_CALLER_NAME), TelecomManager.PRESENTATION_ALLOWED);

docs/android-installation.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import io.wazo.callkeep.RNCallKeepModule; // Add this import line with others
4040

4141
public class MainActivity extends ReactActivity {
4242
// ...
43-
43+
4444
// Permission results
4545
@Override
4646
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) {
@@ -61,8 +61,10 @@ public class MainActivity extends ReactActivity {
6161
```xml
6262
<uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE"/>
6363
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
64+
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
65+
<uses-permission android:name="android.permission.CALL_PHONE" />
6466

65-
<application>
67+
<application>
6668
// ...
6769
<service android:name="io.wazo.callkeep.VoiceConnectionService"
6870
android:label="Wazo"

index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class RNCallKeep {
5050

5151
startCall(uuid, handle, handleType = 'number', hasVideo = false, contactIdentifier) {
5252
if (!isIOS) {
53-
// Can't start a call directly on Android
53+
RNCallKeepModule.startCall(handle, contactIdentifier);
5454
return;
5555
}
5656

0 commit comments

Comments
 (0)