Skip to content

Commit 619fb95

Browse files
authored
Merge pull request #321 from react-native-webrtc/android_11_foreground_service
Start a foreground service to be able to get audio on Android 11 bg
2 parents c3ade44 + b8bc891 commit 619fb95

File tree

6 files changed

+103
-5
lines changed

6 files changed

+103
-5
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,14 @@ const options = {
4747
cancelButton: 'Cancel',
4848
okButton: 'ok',
4949
imageName: 'phone_account_icon',
50-
additionalPermissions: [PermissionsAndroid.PERMISSIONS.example]
50+
additionalPermissions: [PermissionsAndroid.PERMISSIONS.example],
51+
// Required to get audio in background when using Android 11
52+
foregroundService: {
53+
channelId: 'com.company.my',
54+
channelName: 'Foreground service for my app',
55+
notificationTitle: 'My app is running on background',
56+
notiticationIcon: 'Path to the resource icon of the notification',
57+
},
5158
}
5259
};
5360

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

134+
### setForegroundServiceSettings
135+
_This feature is available only on Android._
136+
137+
Configures the [Foreground Service](https://developer.android.com/about/versions/11/privacy/foreground-services) used for Android 11 to get microphone access on background.
138+
Similar to set the `foregroundService` key in the `setup()` method.
139+
140+
```js
141+
RNCallKeep.setForegroundServiceSettings({
142+
channelId: 'com.company.my',
143+
channelName: 'Foreground service for my app',
144+
notificationTitle: 'My app is running on background',
145+
notiticationIcon: 'Path to the resource icon of the notification',
146+
});
147+
```
148+
127149
### canMakeMultipleCalls
128150
_This feature is available only on Android._
129151

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

830+
## Android 11
831+
832+
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.
833+
834+
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).
835+
808836
## Debug
809837

810838
### Android

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ public void setup(ReadableMap options) {
128128
this.registerEvents();
129129
VoiceConnectionService.setAvailable(true);
130130
}
131+
132+
VoiceConnectionService.setSettings(options);
131133
}
132134

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

394+
@ReactMethod
395+
public void setForegroundServiceSettings(ReadableMap settings) {
396+
VoiceConnectionService.setSettings(settings);
397+
}
398+
392399
@ReactMethod
393400
public void canMakeMultipleCalls(Boolean allow) {
394401
VoiceConnectionService.setCanMakeMultipleCalls(allow);

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

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
package io.wazo.callkeep;
1919

2020
import android.annotation.TargetApi;
21+
import android.app.ActivityManager;
22+
import android.app.ActivityManager.RunningTaskInfo;
23+
import android.app.Notification;
24+
import android.app.NotificationChannel;
25+
import android.app.NotificationManager;
26+
import android.content.res.Resources;
2127
import android.content.Intent;
2228
import android.content.Context;
2329
import android.content.ComponentName;
@@ -27,6 +33,7 @@
2733
import android.os.Handler;
2834
import android.speech.tts.Voice;
2935
import androidx.annotation.Nullable;
36+
import android.support.v4.app.NotificationCompat;
3037
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
3138
import android.telecom.CallAudioState;
3239
import android.telecom.Connection;
@@ -37,10 +44,8 @@
3744
import android.telecom.TelecomManager;
3845
import android.util.Log;
3946

40-
import android.app.ActivityManager;
41-
import android.app.ActivityManager.RunningTaskInfo;
42-
4347
import com.facebook.react.HeadlessJsTaskService;
48+
import com.facebook.react.bridge.ReadableMap;
4449

4550
import java.util.ArrayList;
4651
import java.util.HashMap;
@@ -51,6 +56,7 @@
5156
import java.util.UUID;
5257
import java.util.stream.Collectors;
5358

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

115+
public static void setSettings(ReadableMap settings) {
116+
_settings = settings;
117+
}
118+
108119
public static void setCanMakeMultipleCalls(Boolean allow) {
109120
VoiceConnectionService.canMakeMultipleCalls = allow;
110121
}
@@ -133,6 +144,8 @@ public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManage
133144
incomingCallConnection.setRinging();
134145
incomingCallConnection.setInitialized();
135146

147+
startForegroundService();
148+
136149
return incomingCallConnection;
137150
}
138151

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

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

219+
private void startForegroundService() {
220+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
221+
// Foreground services not required before SDK 28
222+
return;
223+
}
224+
if (_settings == null || !_settings.hasKey("foregroundService")) {
225+
Log.d(TAG, "Not creating foregroundService because not configured");
226+
return;
227+
}
228+
ReadableMap foregroundSettings = _settings.getMap("foregroundService");
229+
String NOTIFICATION_CHANNEL_ID = foregroundSettings.getString("channelId");
230+
String channelName = foregroundSettings.getString("channelName");
231+
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
232+
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
233+
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
234+
assert manager != null;
235+
manager.createNotificationChannel(chan);
236+
237+
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
238+
notificationBuilder.setOngoing(true)
239+
.setContentTitle(foregroundSettings.getString("notificationTitle"))
240+
.setPriority(NotificationManager.IMPORTANCE_MIN)
241+
.setCategory(Notification.CATEGORY_SERVICE);
242+
243+
if (foregroundSettings.hasKey("notificationIcon")) {
244+
Context context = this.getApplicationContext();
245+
Resources res = context.getResources();
246+
String smallIcon = foregroundSettings.getString("notificationIcon");
247+
notificationBuilder.setSmallIcon(res.getIdentifier(smallIcon, "mipmap", context.getPackageName()));
248+
}
249+
250+
Notification notification = notificationBuilder.build();
251+
startForeground(FOREGROUND_SERVICE_TYPE_MICROPHONE, notification);
252+
}
253+
204254
private void wakeUpApplication(String uuid, String number, String displayName) {
205255
Intent headlessIntent = new Intent(
206256
this.getApplicationContext(),

docs/android-installation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ public class MainActivity extends ReactActivity {
7171
// ...
7272
<service android:name="io.wazo.callkeep.VoiceConnectionService"
7373
android:label="Wazo"
74-
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
74+
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
75+
android:foregroundServiceType="camera|microphone">>
7576
<intent-filter>
7677
<action android:name="android.telecom.ConnectionService" />
7778
</intent-filter>

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ declare module 'react-native-callkeep' {
138138
*/
139139
static setAvailable(active: boolean): void
140140

141+
static setForegroundServiceSettings(settings: Object): void
142+
141143
static canMakeMultipleCalls(allow: boolean): void
142144

143145
static setCurrentCallActive(callUUID: string): void

index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ class RNCallKeep {
190190
RNCallKeepModule.setAvailable(state);
191191
};
192192

193+
setForegroundServiceSettings = (settings) => {
194+
if (isIOS) {
195+
return;
196+
}
197+
198+
RNCallKeepModule.setForegroundServiceSettings({
199+
foregroundService: settings,
200+
});
201+
};
202+
193203
canMakeMultipleCalls = (state) => {
194204
if (isIOS) {
195205
return;

0 commit comments

Comments
 (0)