diff --git a/README.md b/README.md index 4c51ebde..a96d778a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java index ad01429f..4deb5cbd 100644 --- a/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java +++ b/android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java @@ -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); diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java index 4419cf57..1d0eb5b9 100644 --- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java +++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java @@ -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 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(), diff --git a/docs/android-installation.md b/docs/android-installation.md index 9238ab9d..71c638d8 100644 --- a/docs/android-installation.md +++ b/docs/android-installation.md @@ -71,7 +71,8 @@ public class MainActivity extends ReactActivity { // ... + android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" + android:foregroundServiceType="camera|microphone">> diff --git a/index.d.ts b/index.d.ts index 2090fe29..fa75eb98 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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 diff --git a/index.js b/index.js index 88a9d37c..72185e2a 100644 --- a/index.js +++ b/index.js @@ -190,6 +190,16 @@ class RNCallKeep { RNCallKeepModule.setAvailable(state); }; + setForegroundServiceSettings = (settings) => { + if (isIOS) { + return; + } + + RNCallKeepModule.setForegroundServiceSettings({ + foregroundService: settings, + }); + }; + canMakeMultipleCalls = (state) => { if (isIOS) { return;