Skip to content

Start a foreground service to be able to get audio on Android 11 bg #321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 23, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -47,7 +47,14 @@ const options = {
cancelButton: 'Cancel',
okButton: 'ok',
imageName: 'phone_account_icon',
additionalPermissions: [PermissionsAndroid.PERMISSIONS.example]
additionalPermissions: [PermissionsAndroid.PERMISSIONS.example],
// Required to get audio in background when using Android 11
foregroundService: {
channelId: 'com.company.my',
channelName: 'Foreground service for my app',
notificationTitle: 'My app is running on background',
notiticationIcon: 'Path to the resource icon of the notification',
},
}
};

@@ -124,6 +131,21 @@ Eg: When your used log out (or the connection to your server is broken, etc..),
RNCallKeep.setAvailable(true);
```

### setForegroundServiceSettings
_This feature is available only on Android._

Configures the [Foreground Service](https://developer.android.com/about/versions/11/privacy/foreground-services) used for Android 11 to get microphone access on background.
Similar to set the `foregroundService` key in the `setup()` method.

```js
RNCallKeep.setForegroundServiceSettings({
channelId: 'com.company.my',
channelName: 'Foreground service for my app',
notificationTitle: 'My app is running on background',
notiticationIcon: 'Path to the resource icon of the notification',
});
```

### canMakeMultipleCalls
_This feature is available only on Android._

@@ -805,6 +827,12 @@ Since iOS 13, you'll have to report the incoming calls that wakes up your applic
}
```

## Android 11

Since Android 11, your application [requires to start a foregroundService](https://developer.android.com/about/versions/11/privacy/foreground-services) in order to access the microphone in background.

You have to set the `foregroundService` key in the [`setup()`](#setup) method and add a `foregroundServiceType` in the [`AndroidManifest` file](docs/android-installation.md#android-common-step-installation).

## Debug

### Android
7 changes: 7 additions & 0 deletions android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
Original file line number Diff line number Diff line change
@@ -128,6 +128,8 @@ public void setup(ReadableMap options) {
this.registerEvents();
VoiceConnectionService.setAvailable(true);
}

VoiceConnectionService.setSettings(options);
}

@ReactMethod
@@ -389,6 +391,11 @@ public void setAvailable(Boolean active) {
VoiceConnectionService.setAvailable(active);
}

@ReactMethod
public void setForegroundServiceSettings(ReadableMap settings) {
VoiceConnectionService.setSettings(settings);
}

@ReactMethod
public void canMakeMultipleCalls(Boolean allow) {
VoiceConnectionService.setCanMakeMultipleCalls(allow);
56 changes: 53 additions & 3 deletions android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java
Original file line number Diff line number Diff line change
@@ -18,6 +18,12 @@
package io.wazo.callkeep;

import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.res.Resources;
import android.content.Intent;
import android.content.Context;
import android.content.ComponentName;
@@ -27,6 +33,7 @@
import android.os.Handler;
import android.speech.tts.Voice;
import androidx.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.telecom.CallAudioState;
import android.telecom.Connection;
@@ -37,10 +44,8 @@
import android.telecom.TelecomManager;
import android.util.Log;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;

import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.ReadableMap;

import java.util.ArrayList;
import java.util.HashMap;
@@ -51,6 +56,7 @@
import java.util.UUID;
import java.util.stream.Collectors;

import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static io.wazo.callkeep.Constants.ACTION_AUDIO_SESSION;
import static io.wazo.callkeep.Constants.ACTION_ONGOING_CALL;
import static io.wazo.callkeep.Constants.ACTION_CHECK_REACHABILITY;
@@ -70,6 +76,7 @@ public class VoiceConnectionService extends ConnectionService {
private static String notReachableCallUuid;
private static ConnectionRequest currentConnectionRequest;
private static PhoneAccountHandle phoneAccountHandle;
private static ReadableMap _settings;
private static String TAG = "RNCK:VoiceConnectionService";
public static Map<String, VoiceConnection> currentConnections = new HashMap<>();
public static Boolean hasOutgoingCall = false;
@@ -105,6 +112,10 @@ public static void setAvailable(Boolean value) {
isAvailable = value;
}

public static void setSettings(ReadableMap settings) {
_settings = settings;
}

public static void setCanMakeMultipleCalls(Boolean allow) {
VoiceConnectionService.canMakeMultipleCalls = allow;
}
@@ -133,6 +144,8 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage
incomingCallConnection.setRinging();
incomingCallConnection.setInitialized();

startForegroundService();

return incomingCallConnection;
}

@@ -185,6 +198,8 @@ private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Bool
outgoingCallConnection.setAudioModeIsVoip(true);
outgoingCallConnection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED);

startForegroundService();

// ‍️Weirdly on some Samsung phones (A50, S9...) using `setInitialized` will not display the native UI ...
// when making a call from the native Phone application. The call will still be displayed correctly without it.
if (!Build.MANUFACTURER.equalsIgnoreCase("Samsung")) {
@@ -201,6 +216,41 @@ private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Bool
return outgoingCallConnection;
}

private void startForegroundService() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// Foreground services not required before SDK 28
return;
}
if (_settings == null || !_settings.hasKey("foregroundService")) {
Log.d(TAG, "Not creating foregroundService because not configured");
return;
}
ReadableMap foregroundSettings = _settings.getMap("foregroundService");
String NOTIFICATION_CHANNEL_ID = foregroundSettings.getString("channelId");
String channelName = foregroundSettings.getString("channelName");
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);

NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
notificationBuilder.setOngoing(true)
.setContentTitle(foregroundSettings.getString("notificationTitle"))
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE);

if (foregroundSettings.hasKey("notificationIcon")) {
Context context = this.getApplicationContext();
Resources res = context.getResources();
String smallIcon = foregroundSettings.getString("notificationIcon");
notificationBuilder.setSmallIcon(res.getIdentifier(smallIcon, "mipmap", context.getPackageName()));
}

Notification notification = notificationBuilder.build();
startForeground(FOREGROUND_SERVICE_TYPE_MICROPHONE, notification);
}

private void wakeUpApplication(String uuid, String number, String displayName) {
Intent headlessIntent = new Intent(
this.getApplicationContext(),
3 changes: 2 additions & 1 deletion docs/android-installation.md
Original file line number Diff line number Diff line change
@@ -71,7 +71,8 @@ public class MainActivity extends ReactActivity {
// ...
<service android:name="io.wazo.callkeep.VoiceConnectionService"
android:label="Wazo"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:foregroundServiceType="camera|microphone">>
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -138,6 +138,8 @@ declare module 'react-native-callkeep' {
*/
static setAvailable(active: boolean): void

static setForegroundServiceSettings(settings: Object): void

static canMakeMultipleCalls(allow: boolean): void

static setCurrentCallActive(callUUID: string): void
10 changes: 10 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -190,6 +190,16 @@ class RNCallKeep {
RNCallKeepModule.setAvailable(state);
};

setForegroundServiceSettings = (settings) => {
if (isIOS) {
return;
}

RNCallKeepModule.setForegroundServiceSettings({
foregroundService: settings,
});
};

canMakeMultipleCalls = (state) => {
if (isIOS) {
return;