diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4351c489a5..ad3118218e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1396,6 +1396,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/SafeForegroundService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/SafeForegroundService.kt
index 187b9104e3..004beadc25 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/SafeForegroundService.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/SafeForegroundService.kt
@@ -9,6 +9,7 @@ import android.app.Notification
import android.app.Service
import android.content.Context
import android.content.Intent
+import android.os.Bundle
import android.os.IBinder
import androidx.core.app.ServiceCompat
import org.signal.core.util.logging.Log
@@ -39,7 +40,7 @@ abstract class SafeForegroundService : Service() {
* @return False if we tried to start the service but failed, otherwise true.
*/
@CheckReturnValue
- fun start(context: Context, serviceClass: Class): Boolean {
+ fun start(context: Context, serviceClass: Class, extras: Bundle = Bundle.EMPTY): Boolean {
stateLock.withLock {
val state = currentState(serviceClass)
@@ -57,7 +58,10 @@ abstract class SafeForegroundService : Service() {
try {
ForegroundServiceUtil.startWhenCapable(
context = context,
- intent = Intent(context, serviceClass).apply { action = ACTION_START }
+ intent = Intent(context, serviceClass).apply {
+ action = ACTION_START
+ putExtras(extras)
+ }
)
true
} catch (e: UnableToStartException) {
@@ -79,14 +83,15 @@ abstract class SafeForegroundService : Service() {
* Safely stops the service by starting it with an action to stop itself.
* This is done to prevent scenarios where you stop the service while
* a start is pending, preventing the posting of a foreground notification.
+ * @return true if service was running previously
*/
- fun stop(context: Context, serviceClass: Class) {
+ fun stop(context: Context, serviceClass: Class): Boolean {
stateLock.withLock {
val state = currentState(serviceClass)
Log.d(TAG, "[stop] Current state: $state")
- when (state) {
+ return when (state) {
State.STARTING -> {
Log.d(TAG, "[stop] Stopping service.")
states[serviceClass] = State.STOPPING
@@ -99,16 +104,19 @@ abstract class SafeForegroundService : Service() {
Log.w(TAG, "Failed to start service class $serviceClass", e)
states[serviceClass] = State.STOPPED
}
+ true
}
State.STOPPED,
State.STOPPING -> {
Log.d(TAG, "[stop] No need to stop the service. Current state: $state")
+ false
}
State.NEEDS_RESTART -> {
Log.i(TAG, "[stop] Clearing pending restart.")
states[serviceClass] = State.STOPPING
+ false
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallManager.kt
new file mode 100644
index 0000000000..de3b610a4f
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallManager.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2024 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.service.webrtc
+
+import android.app.Notification
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.telephony.PhoneStateListener
+import android.telephony.TelephonyManager
+import androidx.annotation.MainThread
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.os.bundleOf
+import org.signal.core.util.ThreadUtil
+import org.signal.core.util.logging.Log
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.jobs.UnableToStartException
+import org.thoughtcrime.securesms.recipients.Recipient
+import org.thoughtcrime.securesms.recipients.RecipientId
+import org.thoughtcrime.securesms.service.SafeForegroundService
+import org.thoughtcrime.securesms.util.TelephonyUtil
+import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder
+import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager
+import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCommand
+import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
+import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.Companion.create
+import org.thoughtcrime.securesms.webrtc.locks.LockManager
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.minutes
+
+/**
+ * Entry point for [SignalCallManager] and friends to interact with the Android system as
+ * previously done via [WebRtcCallService].
+ *
+ * This tries to limit the use of a foreground service until a call has been fully established
+ * and the user has likely foregrounded us by accepting a call.
+ */
+class ActiveCallManager(
+ private val application: Context
+) : SignalAudioManager.EventListener {
+
+ companion object {
+ private val TAG = Log.tag(ActiveCallManager::class.java)
+ }
+
+ private val callManager = ApplicationDependencies.getSignalCallManager()
+
+ private var networkReceiver: NetworkReceiver? = null
+ private var powerButtonReceiver: PowerButtonReceiver? = null
+ private var uncaughtExceptionHandlerManager: UncaughtExceptionHandlerManager? = null
+ private val webSocketKeepAliveTask: WebSocketKeepAliveTask = WebSocketKeepAliveTask()
+ private var signalAudioManager: SignalAudioManager? = null
+ private var previousNotificationId = -1
+
+ init {
+ registerUncaughtExceptionHandler()
+ registerNetworkReceiver()
+
+ webSocketKeepAliveTask.start()
+ }
+
+ fun stop() {
+ Log.v(TAG, "stop")
+
+ uncaughtExceptionHandlerManager?.unregister()
+ uncaughtExceptionHandlerManager = null
+
+ signalAudioManager?.shutdown()
+ signalAudioManager = null
+
+ unregisterNetworkReceiver()
+ unregisterPowerButtonReceiver()
+
+ webSocketKeepAliveTask.stop()
+
+ if (!ActiveCallForegroundService.stop(application) && previousNotificationId != -1) {
+ NotificationManagerCompat.from(application).cancel(previousNotificationId)
+ }
+ }
+
+ fun update(type: Int, recipientId: RecipientId, isVideoCall: Boolean) {
+ Log.i(TAG, "update $type $recipientId $isVideoCall")
+
+ val notificationId = CallNotificationBuilder.getNotificationId(type)
+
+ if (previousNotificationId != notificationId && previousNotificationId != -1) {
+ NotificationManagerCompat.from(application).cancel(previousNotificationId)
+ }
+
+ previousNotificationId = notificationId
+
+ if (type != CallNotificationBuilder.TYPE_ESTABLISHED) {
+ val notification = CallNotificationBuilder.getCallInProgressNotification(application, type, Recipient.resolved(recipientId), isVideoCall, false)
+ NotificationManagerCompat.from(application).notify(notificationId, notification)
+ } else {
+ ActiveCallForegroundService.start(application, recipientId, isVideoCall)
+ }
+ }
+
+ fun sendAudioCommand(audioCommand: AudioManagerCommand) {
+ if (signalAudioManager == null) {
+ signalAudioManager = create(application, this)
+ }
+
+ Log.i(TAG, "Sending audio command [" + audioCommand.javaClass.simpleName + "] to " + signalAudioManager?.javaClass?.simpleName)
+ signalAudioManager!!.handleCommand(audioCommand)
+ }
+
+ fun changePowerButton(enabled: Boolean) {
+ if (enabled) {
+ registerPowerButtonReceiver()
+ } else {
+ unregisterPowerButtonReceiver()
+ }
+ }
+
+ private fun registerUncaughtExceptionHandler() {
+ uncaughtExceptionHandlerManager = UncaughtExceptionHandlerManager()
+ uncaughtExceptionHandlerManager!!.registerHandler(ProximityLockRelease(callManager.lockManager))
+ }
+
+ private fun registerNetworkReceiver() {
+ if (networkReceiver == null) {
+ networkReceiver = NetworkReceiver()
+ application.registerReceiver(networkReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
+ }
+ }
+
+ private fun unregisterNetworkReceiver() {
+ if (networkReceiver != null) {
+ application.unregisterReceiver(networkReceiver)
+ networkReceiver = null
+ }
+ }
+
+ private fun registerPowerButtonReceiver() {
+ if (!AndroidTelecomUtil.telecomSupported && powerButtonReceiver == null) {
+ powerButtonReceiver = PowerButtonReceiver()
+ application.registerReceiver(powerButtonReceiver, IntentFilter(Intent.ACTION_SCREEN_OFF))
+ }
+ }
+
+ private fun unregisterPowerButtonReceiver() {
+ if (powerButtonReceiver != null) {
+ application.unregisterReceiver(powerButtonReceiver)
+ powerButtonReceiver = null
+ }
+ }
+
+ override fun onAudioDeviceChanged(activeDevice: SignalAudioManager.AudioDevice, devices: Set) {
+ callManager.onAudioDeviceChanged(activeDevice, devices)
+ }
+
+ override fun onBluetoothPermissionDenied() {
+ callManager.onBluetoothPermissionDenied()
+ }
+
+ /** Foreground service started only after a call is established */
+ class ActiveCallForegroundService : SafeForegroundService() {
+ companion object {
+ private const val EXTRA_RECIPIENT_ID = "RECIPIENT_ID"
+ private const val EXTRA_IS_VIDEO_CALL = "IS_VIDEO_CALL"
+
+ fun start(context: Context, recipientId: RecipientId, isVideoCall: Boolean) {
+ val extras = bundleOf(
+ EXTRA_RECIPIENT_ID to recipientId,
+ EXTRA_IS_VIDEO_CALL to isVideoCall
+ )
+
+ if (!SafeForegroundService.start(context, ActiveCallForegroundService::class.java, extras)) {
+ throw UnableToStartException(Exception())
+ }
+ }
+
+ fun stop(context: Context): Boolean {
+ return SafeForegroundService.stop(context, ActiveCallForegroundService::class.java)
+ }
+ }
+
+ override val tag: String
+ get() = TAG
+
+ override val notificationId: Int
+ get() = CallNotificationBuilder.WEBRTC_NOTIFICATION
+
+ private var hangUpRtcOnDeviceCallAnswered: PhoneStateListener? = null
+ private var notification: Notification? = null
+
+ override fun onCreate() {
+ super.onCreate()
+ hangUpRtcOnDeviceCallAnswered = HangUpRtcOnPstnCallAnsweredListener()
+
+ if (!AndroidTelecomUtil.telecomSupported) {
+ try {
+ TelephonyUtil.getManager(application).listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
+ } catch (e: SecurityException) {
+ Log.w(TAG, "Failed to listen to PSTN call answers!", e)
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ if (!AndroidTelecomUtil.telecomSupported) {
+ TelephonyUtil.getManager(application).listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE)
+ }
+ }
+
+ override fun getForegroundNotification(intent: Intent): Notification {
+ if (notification != null) {
+ return notification!!
+ } else if (!intent.hasExtra(EXTRA_RECIPIENT_ID)) {
+ return CallNotificationBuilder.getStoppingNotification(this)
+ }
+
+ val recipientId: RecipientId = intent.getParcelableExtra(EXTRA_RECIPIENT_ID)!!
+ val isVideoCall = intent.getBooleanExtra(EXTRA_IS_VIDEO_CALL, false)
+
+ notification = CallNotificationBuilder.getCallInProgressNotification(
+ this,
+ CallNotificationBuilder.TYPE_ESTABLISHED,
+ Recipient.resolved(recipientId),
+ isVideoCall,
+ false
+ )
+
+ return notification!!
+ }
+
+ @Suppress("deprecation")
+ private class HangUpRtcOnPstnCallAnsweredListener : PhoneStateListener() {
+ override fun onCallStateChanged(state: Int, phoneNumber: String) {
+ super.onCallStateChanged(state, phoneNumber)
+ if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
+ hangup()
+ Log.i(TAG, "Device phone call ended Signal call.")
+ }
+ }
+
+ private fun hangup() {
+ ApplicationDependencies.getSignalCallManager().localHangup()
+ }
+ }
+ }
+
+ class ActiveCallServiceReceiver : BroadcastReceiver() {
+
+ companion object {
+ const val ACTION_DENY = "org.thoughtcrime.securesms.service.webrtc.ActiveCallAction.DENY"
+ const val ACTION_HANGUP = "org.thoughtcrime.securesms.service.webrtc.ActiveCallAction.HANGUP"
+ }
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ Log.d(TAG, "action: ${intent?.action}")
+ when (intent?.action) {
+ ACTION_DENY -> ApplicationDependencies.getSignalCallManager().denyCall()
+ ACTION_HANGUP -> ApplicationDependencies.getSignalCallManager().localHangup()
+ }
+ }
+ }
+
+ /**
+ * Periodically request the web socket stay open if we are doing anything call related.
+ */
+ private class WebSocketKeepAliveTask : Runnable {
+
+ companion object {
+ private val REQUEST_WEBSOCKET_STAY_OPEN_DELAY: Duration = 1.minutes
+ private val WEBSOCKET_KEEP_ALIVE_TOKEN: String = WebSocketKeepAliveTask::class.java.simpleName
+ }
+
+ private var keepRunning = false
+
+ @MainThread
+ fun start() {
+ if (!keepRunning) {
+ keepRunning = true
+ run()
+ }
+ }
+
+ @MainThread
+ fun stop() {
+ keepRunning = false
+ ThreadUtil.cancelRunnableOnMain(this)
+ ApplicationDependencies.getIncomingMessageObserver().removeKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN)
+ }
+
+ @MainThread
+ override fun run() {
+ if (keepRunning) {
+ ApplicationDependencies.getIncomingMessageObserver().registerKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN)
+ ThreadUtil.runOnMainDelayed(this, REQUEST_WEBSOCKET_STAY_OPEN_DELAY.inWholeMilliseconds)
+ }
+ }
+ }
+
+ private class NetworkReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetworkInfo = connectivityManager.activeNetworkInfo
+
+ ApplicationDependencies.getSignalCallManager().apply {
+ networkChange(activeNetworkInfo != null && activeNetworkInfo.isConnected)
+ dataModeUpdate()
+ }
+ }
+ }
+
+ private class PowerButtonReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (Intent.ACTION_SCREEN_OFF == intent.action) {
+ ApplicationDependencies.getSignalCallManager().screenOff()
+ }
+ }
+ }
+
+ private class ProximityLockRelease(private val lockManager: LockManager) : Thread.UncaughtExceptionHandler {
+ override fun uncaughtException(thread: Thread, throwable: Throwable) {
+ Log.i(TAG, "Uncaught exception - releasing proximity lock", throwable)
+ lockManager.updatePhoneState(LockManager.PhoneState.IDLE)
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java
index 4a7e866d99..27dfa2b93a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.app.Notification;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -18,6 +19,7 @@ import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import org.signal.core.util.PendingIntentFlags;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
@@ -25,6 +27,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager;
@@ -84,7 +87,18 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
private Disposable lastNotificationDisposable = Disposable.disposed();
private boolean stopping = false;
- public static void update(@NonNull Context context, int type, @NonNull RecipientId recipientId, boolean isVideoCall) {
+ private static ActiveCallManager activeCallManager = null;
+
+ public synchronized static void update(@NonNull Context context, int type, @NonNull RecipientId recipientId, boolean isVideoCall) {
+ if (FeatureFlags.useActiveCallManager()) {
+ if (activeCallManager == null) {
+ activeCallManager = new ActiveCallManager(context);
+ }
+ activeCallManager.update(type, recipientId, isVideoCall);
+
+ return;
+ }
+
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_UPDATE)
.putExtra(EXTRA_UPDATE_TYPE, type)
@@ -95,36 +109,84 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
}
public static void denyCall(@NonNull Context context) {
- ForegroundServiceUtil.tryToStartWhenCapable(context, denyCallIntent(context), FOREGROUND_SERVICE_TIMEOUT);
+ if (FeatureFlags.useActiveCallManager()) {
+ ApplicationDependencies.getSignalCallManager().denyCall();
+ return;
+ }
+
+ ForegroundServiceUtil.tryToStartWhenCapable(context, new Intent(context, WebRtcCallService.class).setAction(ACTION_DENY_CALL), FOREGROUND_SERVICE_TIMEOUT);
}
public static void hangup(@NonNull Context context) {
- ForegroundServiceUtil.tryToStartWhenCapable(context, hangupIntent(context), FOREGROUND_SERVICE_TIMEOUT);
+ if (FeatureFlags.useActiveCallManager()) {
+ ApplicationDependencies.getSignalCallManager().localHangup();
+ return;
+ }
+
+ ForegroundServiceUtil.tryToStartWhenCapable(context, new Intent(context, WebRtcCallService.class).setAction(ACTION_LOCAL_HANGUP), FOREGROUND_SERVICE_TIMEOUT);
}
- public static void stop(@NonNull Context context) {
+ public synchronized static void stop(@NonNull Context context) {
+ if (FeatureFlags.useActiveCallManager()) {
+ if (activeCallManager != null) {
+ activeCallManager.stop();
+ activeCallManager = null;
+ }
+ return;
+ }
+
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_STOP);
ForegroundServiceUtil.tryToStartWhenCapable(context, intent, FOREGROUND_SERVICE_TIMEOUT);
}
- public static @NonNull Intent denyCallIntent(@NonNull Context context) {
- return new Intent(context, WebRtcCallService.class).setAction(ACTION_DENY_CALL);
+ public synchronized static @NonNull PendingIntent denyCallIntent(@NonNull Context context) {
+ if (FeatureFlags.useActiveCallManager()) {
+ Intent intent = new Intent(context, ActiveCallManager.ActiveCallServiceReceiver.class);
+ intent.setAction(ActiveCallManager.ActiveCallServiceReceiver.ACTION_DENY);
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.mutable());
+ }
+
+ return getServicePendingIntent(context, new Intent(context, WebRtcCallService.class).setAction(ACTION_DENY_CALL));
}
- public static @NonNull Intent hangupIntent(@NonNull Context context) {
- return new Intent(context, WebRtcCallService.class).setAction(ACTION_LOCAL_HANGUP);
+ public synchronized static @NonNull PendingIntent hangupIntent(@NonNull Context context) {
+ if (FeatureFlags.useActiveCallManager()) {
+ Intent intent = new Intent(context, ActiveCallManager.ActiveCallServiceReceiver.class);
+ intent.setAction(ActiveCallManager.ActiveCallServiceReceiver.ACTION_HANGUP);
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.mutable());
+ }
+
+ return getServicePendingIntent(context, new Intent(context, WebRtcCallService.class).setAction(ACTION_LOCAL_HANGUP));
}
- public static void sendAudioManagerCommand(@NonNull Context context, @NonNull AudioManagerCommand command) {
+ public synchronized static void sendAudioManagerCommand(@NonNull Context context, @NonNull AudioManagerCommand command) {
+ if (FeatureFlags.useActiveCallManager()) {
+ if (activeCallManager == null) {
+ activeCallManager = new ActiveCallManager(context);
+ }
+ activeCallManager.sendAudioCommand(command);
+
+ return;
+ }
+
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_SEND_AUDIO_COMMAND)
.putExtra(EXTRA_AUDIO_COMMAND, command);
ForegroundServiceUtil.tryToStartWhenCapable(context, intent, FOREGROUND_SERVICE_TIMEOUT);
}
- public static void changePowerButtonReceiver(@NonNull Context context, boolean register) {
+ public synchronized static void changePowerButtonReceiver(@NonNull Context context, boolean register) {
+ if (FeatureFlags.useActiveCallManager()) {
+ if (activeCallManager == null) {
+ activeCallManager = new ActiveCallManager(context);
+ }
+ activeCallManager.changePowerButton(register);
+
+ return;
+ }
+
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_CHANGE_POWER_BUTTON)
.putExtra(EXTRA_ENABLED, register);
@@ -337,6 +399,11 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
callManager.onBluetoothPermissionDenied();
}
+ public static PendingIntent getServicePendingIntent(@NonNull Context context, @NonNull Intent intent) {
+ return Build.VERSION.SDK_INT >= 26 ? PendingIntent.getForegroundService(context, 0, intent, PendingIntentFlags.mutable())
+ : PendingIntent.getService(context, 0, intent, PendingIntentFlags.mutable());
+ }
+
@SuppressWarnings("deprecation")
private class HangUpRtcOnPstnCallAnsweredListener extends PhoneStateListener {
@Override
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
index 8d7259051c..2a6debaf06 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
@@ -117,7 +117,8 @@ public final class FeatureFlags {
public static final String SEPA_ENABLED_REGIONS = "global.donations.sepaEnabledRegions";
private static final String CALLING_REACTIONS = "android.calling.reactions";
private static final String NOTIFICATION_THUMBNAIL_BLOCKLIST = "android.notificationThumbnailProductBlocklist";
- private static final String CALLING_RAISE_HAND = "android.calling.raiseHand";
+ private static final String CALLING_RAISE_HAND = "android.calling.raiseHand";
+ private static final String USE_ACTIVE_CALL_MANAGER = "android.calling.useActiveCallManager";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@@ -188,7 +189,8 @@ public final class FeatureFlags {
CALLING_REACTIONS,
NOTIFICATION_THUMBNAIL_BLOCKLIST,
CALLING_RAISE_HAND,
- PHONE_NUMBER_PRIVACY
+ PHONE_NUMBER_PRIVACY,
+ USE_ACTIVE_CALL_MANAGER
);
@VisibleForTesting
@@ -679,6 +681,11 @@ public final class FeatureFlags {
return getString(NOTIFICATION_THUMBNAIL_BLOCKLIST, "");
}
+ /** Whether or not to use active call manager instead of WebRtcCallService. */
+ public static boolean useActiveCallManager() {
+ return getBoolean(USE_ACTIVE_CALL_MANAGER, false);
+ }
+
/** Only for rendering debug info. */
public static synchronized @NonNull Map getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java
index e60417eedc..36309ed260 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java
@@ -29,7 +29,7 @@ import org.thoughtcrime.securesms.util.ConversationUtil;
public class CallNotificationBuilder {
- private static final int WEBRTC_NOTIFICATION = 313388;
+ public static final int WEBRTC_NOTIFICATION = 313388;
private static final int WEBRTC_NOTIFICATION_RINGING = 313389;
public static final int TYPE_INCOMING_RINGING = 1;
@@ -114,7 +114,7 @@ public class CallNotificationBuilder {
if (deviceVersionSupportsIncomingCallStyle()) {
builder.setStyle(NotificationCompat.CallStyle.forIncomingCall(
person,
- getServicePendingIntent(context, WebRtcCallService.denyCallIntent(context)),
+ WebRtcCallService.denyCallIntent(context),
getActivityPendingIntent(context, isVideoCall ? LaunchCallScreenIntentState.VIDEO : LaunchCallScreenIntentState.AUDIO)
).setIsVideo(isVideoCall));
}
@@ -138,7 +138,7 @@ public class CallNotificationBuilder {
if (deviceVersionSupportsIncomingCallStyle()) {
builder.setStyle(NotificationCompat.CallStyle.forOngoingCall(
person,
- getServicePendingIntent(context, WebRtcCallService.hangupIntent(context))
+ WebRtcCallService.hangupIntent(context)
).setIsVideo(isVideoCall));
}
@@ -214,13 +214,8 @@ public class CallNotificationBuilder {
}
}
- private static PendingIntent getServicePendingIntent(@NonNull Context context, @NonNull Intent intent) {
- return Build.VERSION.SDK_INT >= 26 ? PendingIntent.getForegroundService(context, 0, intent, PendingIntentFlags.mutable())
- : PendingIntent.getService(context, 0, intent, PendingIntentFlags.mutable());
- }
-
- private static NotificationCompat.Action getServiceNotificationAction(Context context, Intent intent, int iconResId, int titleResId) {
- return new NotificationCompat.Action(iconResId, context.getString(titleResId), getServicePendingIntent(context, intent));
+ private static NotificationCompat.Action getServiceNotificationAction(Context context, PendingIntent intent, int iconResId, int titleResId) {
+ return new NotificationCompat.Action(iconResId, context.getString(titleResId), intent);
}
private static PendingIntent getActivityPendingIntent(@NonNull Context context, @NonNull LaunchCallScreenIntentState launchCallScreenIntentState) {