Skip to content

Commit

Permalink
Start a foreground service to ba able to get audio on Android 11 bg
Browse files Browse the repository at this point in the history
  • Loading branch information
manuquentin committed Nov 22, 2020
1 parent c3ade44 commit 16410d5
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 5 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ 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',
},
}
};

Expand Down Expand Up @@ -805,6 +811,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
Expand Down
2 changes: 2 additions & 0 deletions android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public void setup(ReadableMap options) {
this.registerEvents();
VoiceConnectionService.setAvailable(true);
}

VoiceConnectionService.setSettings(options);
}

@ReactMethod
Expand Down
42 changes: 39 additions & 3 deletions android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
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.Intent;
import android.content.Context;
import android.content.ComponentName;
Expand All @@ -27,6 +32,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;
Expand All @@ -37,10 +43,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;
Expand All @@ -51,6 +55,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;
Expand All @@ -70,6 +75,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;
Expand Down Expand Up @@ -105,6 +111,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;
}
Expand Down Expand Up @@ -133,6 +143,8 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage
incomingCallConnection.setRinging();
incomingCallConnection.setInitialized();

startForegroundService();

return incomingCallConnection;
}

Expand Down Expand Up @@ -185,6 +197,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")) {
Expand All @@ -201,6 +215,28 @@ private Connection makeOutgoingCall(ConnectionRequest request, String uuid, Bool
return outgoingCallConnection;
}

private void startForegroundService() {
if (_settings == null || !_settings.hasKey("foregroundService")) {
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);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle(foregroundSettings.getString("notificationTitle"))
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(FOREGROUND_SERVICE_TYPE_MICROPHONE, notification);
}

private void wakeUpApplication(String uuid, String number, String displayName) {
Intent headlessIntent = new Intent(
this.getApplicationContext(),
Expand Down
3 changes: 2 additions & 1 deletion docs/android-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit 16410d5

Please sign in to comment.