mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Introduce ActiveCallManager to prevent android service crashes during call handling.
This commit is contained in:
committed by
Greyson Parrelli
parent
ee19520e1b
commit
96823e944d
@@ -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<out SafeForegroundService>): Boolean {
|
||||
fun start(context: Context, serviceClass: Class<out SafeForegroundService>, 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<out SafeForegroundService>) {
|
||||
fun stop(context: Context, serviceClass: Class<out SafeForegroundService>): 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SignalAudioManager.AudioDevice>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user