From e024541b8a746880c9ccade5180492352cd6f4c2 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 18 Jul 2022 11:17:16 -0400 Subject: [PATCH] Add telecom integration allow list and change processing for outgoing audio calls. --- .../securesms/keyvalue/InternalValues.java | 2 +- .../ActiveCallActionProcessorDelegate.java | 5 ++++ .../service/webrtc/AndroidCallConnection.kt | 20 +++++++++++++++- .../webrtc/AndroidCallConnectionService.kt | 24 ++++++++++++++----- .../service/webrtc/AndroidTelecomUtil.kt | 16 +++++++++---- ...roupNetworkUnavailableActionProcessor.java | 2 +- .../webrtc/GroupPreJoinActionProcessor.java | 2 +- .../webrtc/IncomingCallActionProcessor.java | 2 +- .../IncomingGroupCallActionProcessor.java | 2 +- .../webrtc/OutgoingCallActionProcessor.java | 2 +- ...ctor.kt => RingRtcDynamicConfiguration.kt} | 9 +++++-- .../securesms/util/FeatureFlags.java | 20 ++++++++++++++-- ...RingRtcDynamicConfigurationTest_inList.kt} | 4 ++-- 13 files changed, 86 insertions(+), 24 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/service/webrtc/{AudioProcessingMethodSelector.kt => RingRtcDynamicConfiguration.kt} (84%) rename app/src/test/java/org/thoughtcrime/securesms/service/webrtc/{AudioProcessingMethodSelectorTest_modelInList.kt => RingRtcDynamicConfigurationTest_inList.kt} (87%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java index 42f85943a6..5e1f91d6a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.java @@ -187,7 +187,7 @@ public final class InternalValues extends SignalStoreValues { if (FeatureFlags.internalUser()) { return getBoolean(CALLING_DISABLE_TELECOM, false); } else { - return true; + return false; } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java index 26b85232b6..a748a47903 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java @@ -248,6 +248,11 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor { } return terminate(currentState, activePeer); + } else { + RemotePeer peerByCallId = currentState.getCallInfoState().getPeerByCallId(callId); + if (peerByCallId != null) { + webRtcInteractor.terminateCall(peerByCallId.getId()); + } } return currentState; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnection.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnection.kt index 9ee8e7fa1c..fd554b132b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnection.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnection.kt @@ -19,7 +19,15 @@ import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager * inform us about changes in the telecom system. Created and returned by [AndroidCallConnectionService]. */ @RequiresApi(26) -class AndroidCallConnection(private val context: Context, val recipientId: RecipientId, val isOutgoing: Boolean = false) : Connection() { +class AndroidCallConnection( + private val context: Context, + private val recipientId: RecipientId, + isOutgoing: Boolean, + isVideoCall: Boolean +) : Connection() { + + private var needToResetAudioRoute = isOutgoing && !isVideoCall + private var initialAudioRoute: SignalAudioManager.AudioDevice? = null init { connectionProperties = PROPERTY_SELF_MANAGED @@ -41,6 +49,16 @@ class AndroidCallConnection(private val context: Context, val recipientId: Recip val availableDevices = state.supportedRouteMask.toDevices() ApplicationDependencies.getSignalCallManager().onAudioDeviceChanged(activeDevice, availableDevices) + + if (needToResetAudioRoute) { + if (initialAudioRoute == null) { + initialAudioRoute = activeDevice + } else if (activeDevice == SignalAudioManager.AudioDevice.SPEAKER_PHONE) { + Log.i(TAG, "Resetting audio route from SPEAKER_PHONE to $initialAudioRoute") + AndroidTelecomUtil.selectAudioDevice(recipientId, initialAudioRoute!!) + needToResetAudioRoute = false + } + } } override fun onAnswer(videoState: Int) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnectionService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnectionService.kt index 9ce9720847..031411924a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnectionService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidCallConnectionService.kt @@ -26,12 +26,17 @@ class AndroidCallConnectionService : ConnectionService() { connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest ): Connection { - val (recipientId: RecipientId, callId: Long) = request.getOurExtras() + val (recipientId: RecipientId, callId: Long, isVideoCall: Boolean) = request.getOurExtras() Log.i(TAG, "onCreateIncomingConnection($recipientId)") val recipient = Recipient.resolved(recipientId) val displayName = recipient.getDisplayName(this) - val connection = AndroidCallConnection(applicationContext, recipientId).apply { + val connection = AndroidCallConnection( + context = applicationContext, + recipientId = recipientId, + isOutgoing = false, + isVideoCall = isVideoCall + ).apply { setInitializing() if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact && recipient.e164.isPresent) { setAddress(Uri.fromParts("tel", recipient.e164.get(), null), TelecomManager.PRESENTATION_ALLOWED) @@ -61,10 +66,15 @@ class AndroidCallConnectionService : ConnectionService() { connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest ): Connection { - val (recipientId: RecipientId, callId: Long) = request.getOurExtras() + val (recipientId: RecipientId, callId: Long, isVideoCall: Boolean) = request.getOurExtras() Log.i(TAG, "onCreateOutgoingConnection($recipientId)") - val connection = AndroidCallConnection(applicationContext, recipientId, true).apply { + val connection = AndroidCallConnection( + context = applicationContext, + recipientId = recipientId, + isOutgoing = true, + isVideoCall = isVideoCall + ).apply { videoState = request.videoState extras = request.extras setDialing() @@ -89,6 +99,7 @@ class AndroidCallConnectionService : ConnectionService() { private val TAG: String = Log.tag(AndroidCallConnectionService::class.java) const val KEY_RECIPIENT_ID = "org.thoughtcrime.securesms.RECIPIENT_ID" const val KEY_CALL_ID = "org.thoughtcrime.securesms.CALL_ID" + const val KEY_VIDEO_CALL = "org.thoughtcrime.securesms.VIDEO_CALL" } private fun ConnectionRequest.getOurExtras(): ServiceExtras { @@ -96,9 +107,10 @@ class AndroidCallConnectionService : ConnectionService() { val recipientId: RecipientId = RecipientId.from(ourExtras.getString(KEY_RECIPIENT_ID)!!) val callId: Long = ourExtras.getLong(KEY_CALL_ID) + val isVideoCall: Boolean = ourExtras.getBoolean(KEY_VIDEO_CALL, false) - return ServiceExtras(recipientId, callId) + return ServiceExtras(recipientId, callId, isVideoCall) } - private data class ServiceExtras(val recipientId: RecipientId, val callId: Long) + private data class ServiceExtras(val recipientId: RecipientId, val callId: Long, val isVideoCall: Boolean) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt index bc2a9a12f4..99f358c7db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AndroidTelecomUtil.kt @@ -21,6 +21,7 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager /** @@ -40,7 +41,7 @@ object AndroidTelecomUtil { @JvmStatic val telecomSupported: Boolean get() { - if (Build.VERSION.SDK_INT >= 26 && !systemRejected && !isRestrictedDevice()) { + if (Build.VERSION.SDK_INT >= 26 && !systemRejected && isTelecomAllowedForDevice()) { if (!accountRegistered) { registerPhoneAccount() } @@ -90,6 +91,7 @@ object AndroidTelecomUtil { TelecomManager.EXTRA_INCOMING_CALL_EXTRAS to bundleOf( AndroidCallConnectionService.KEY_RECIPIENT_ID to recipientId.serialize(), AndroidCallConnectionService.KEY_CALL_ID to callId, + AndroidCallConnectionService.KEY_VIDEO_CALL to remoteVideoOffer, TelecomManager.EXTRA_INCOMING_VIDEO_STATE to if (remoteVideoOffer) VideoProfile.STATE_BIDIRECTIONAL else VideoProfile.STATE_AUDIO_ONLY ), TelecomManager.EXTRA_INCOMING_VIDEO_STATE to if (remoteVideoOffer) VideoProfile.STATE_BIDIRECTIONAL else VideoProfile.STATE_AUDIO_ONLY @@ -139,10 +141,11 @@ object AndroidTelecomUtil { if (telecomSupported) { val telecomBundle = bundleOf( TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE to getPhoneAccountHandle(), - TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE to if (isVideoCall) VideoProfile.STATE_BIDIRECTIONAL else VideoProfile.STATE_AUDIO_ONLY, + TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE to VideoProfile.STATE_BIDIRECTIONAL, TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS to bundleOf( AndroidCallConnectionService.KEY_RECIPIENT_ID to recipientId.serialize(), - AndroidCallConnectionService.KEY_CALL_ID to callId + AndroidCallConnectionService.KEY_CALL_ID to callId, + AndroidCallConnectionService.KEY_VIDEO_CALL to isVideoCall ), ) @@ -188,8 +191,11 @@ object AndroidTelecomUtil { return SignalAudioManager.AudioDevice.NONE } - private fun isRestrictedDevice(): Boolean { - return SignalStore.internalValues().callingDisableTelecom() + private fun isTelecomAllowedForDevice(): Boolean { + if (FeatureFlags.internalUser()) { + return !SignalStore.internalValues().callingDisableTelecom() + } + return RingRtcDynamicConfiguration.isTelecomAllowedForDevice() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupNetworkUnavailableActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupNetworkUnavailableActionProcessor.java index 23ffd0ab29..b0ee93453c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupNetworkUnavailableActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupNetworkUnavailableActionProcessor.java @@ -47,7 +47,7 @@ class GroupNetworkUnavailableActionProcessor extends WebRtcActionProcessor { SignalStore.internalValues().groupCallingServer(), new byte[0], null, - AudioProcessingMethodSelector.get(), + RingRtcDynamicConfiguration.getAudioProcessingMethod(), webRtcInteractor.getGroupCallObserver()); return currentState.builder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java index bdff33363a..ea96df7d01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java @@ -46,7 +46,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor { SignalStore.internalValues().groupCallingServer(), new byte[0], AUDIO_LEVELS_INTERVAL, - AudioProcessingMethodSelector.get(), + RingRtcDynamicConfiguration.getAudioProcessingMethod(), webRtcInteractor.getGroupCallObserver()); try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java index 4fa5df3daf..06495f7ec8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java @@ -99,7 +99,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { webRtcInteractor.getCallManager().proceed(activePeer.getCallId(), context, videoState.getLockableEglBase().require(), - AudioProcessingMethodSelector.get(), + RingRtcDynamicConfiguration.getAudioProcessingMethod(), videoState.requireLocalSink(), callParticipant.getVideoSink(), videoState.requireCamera(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java index 1967b42008..2c2ffaa75f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java @@ -172,7 +172,7 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro SignalStore.internalValues().groupCallingServer(), new byte[0], AUDIO_LEVELS_INTERVAL, - AudioProcessingMethodSelector.get(), + RingRtcDynamicConfiguration.getAudioProcessingMethod(), webRtcInteractor.getGroupCallObserver()); try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java index 340f8de853..3e5b605355 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java @@ -143,7 +143,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { webRtcInteractor.getCallManager().proceed(activePeer.getCallId(), context, videoState.getLockableEglBase().require(), - AudioProcessingMethodSelector.get(), + RingRtcDynamicConfiguration.getAudioProcessingMethod(), videoState.requireLocalSink(), callParticipant.getVideoSink(), videoState.requireCamera(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AudioProcessingMethodSelector.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfiguration.kt similarity index 84% rename from app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AudioProcessingMethodSelector.kt rename to app/src/main/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfiguration.kt index 624bf4d8b3..8ed2bd9e87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/AudioProcessingMethodSelector.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfiguration.kt @@ -9,10 +9,10 @@ import org.thoughtcrime.securesms.util.FeatureFlags /** * Utility class to determine which AEC method RingRTC should use. */ -object AudioProcessingMethodSelector { +object RingRtcDynamicConfiguration { @JvmStatic - fun get(): AudioProcessingMethod { + fun getAudioProcessingMethod(): AudioProcessingMethod { if (SignalStore.internalValues().callingAudioProcessingMethod() != AudioProcessingMethod.Default) { return SignalStore.internalValues().callingAudioProcessingMethod() } @@ -30,6 +30,11 @@ object AudioProcessingMethodSelector { } } + fun isTelecomAllowedForDevice(): Boolean { + return modelInList(Build.MANUFACTURER.lowercase(), FeatureFlags.telecomManufacturerAllowList().lowercase()) && + !modelInList(Build.MODEL.lowercase(), FeatureFlags.telecomModelBlockList().lowercase()) + } + private fun isHardwareBlocklisted(): Boolean { return modelInList(Build.MODEL, FeatureFlags.hardwareAecBlocklistModels()) } 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 8f43526982..eac2b364e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -97,6 +97,8 @@ public final class FeatureFlags { private static final String STORIES_AUTO_DOWNLOAD_MAXIMUM = "android.stories.autoDownloadMaximum"; private static final String GIFT_BADGES = "android.giftBadges.3"; private static final String USE_QR_LEGACY_SCAN = "android.qr.legacy_scan"; + private static final String TELECOM_MANUFACTURER_ALLOWLIST = "android.calling.telecomAllowList"; + private static final String TELECOM_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -146,7 +148,9 @@ public final class FeatureFlags { USE_FCM_FOREGROUND_SERVICE, STORIES_AUTO_DOWNLOAD_MAXIMUM, GIFT_BADGES, - USE_QR_LEGACY_SCAN + USE_QR_LEGACY_SCAN, + TELECOM_MANUFACTURER_ALLOWLIST, + TELECOM_MODEL_BLOCKLIST ); @VisibleForTesting @@ -206,7 +210,9 @@ public final class FeatureFlags { USE_AEC3, PAYMENTS_COUNTRY_BLOCKLIST, USE_FCM_FOREGROUND_SERVICE, - USE_QR_LEGACY_SCAN + USE_QR_LEGACY_SCAN, + TELECOM_MANUFACTURER_ALLOWLIST, + TELECOM_MODEL_BLOCKLIST ); /** @@ -477,6 +483,16 @@ public final class FeatureFlags { return getString(SOFTWARE_AEC_BLOCKLIST_MODELS, ""); } + /** A comma-separated list of manufacturers that *should* use Telecom for calling. */ + public static @NonNull String telecomManufacturerAllowList() { + return getString(TELECOM_MANUFACTURER_ALLOWLIST, ""); + } + + /** A comma-separated list of manufacturers that *should* use Telecom for calling. */ + public static @NonNull String telecomModelBlockList() { + return getString(TELECOM_MODEL_BLOCKLIST, ""); + } + /** Whether or not hardware AEC should be used for calling on devices older than API 29. */ public static boolean useHardwareAecIfOlderThanApi29() { return getBoolean(USE_HARDWARE_AEC_IF_OLD, false); diff --git a/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/AudioProcessingMethodSelectorTest_modelInList.kt b/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfigurationTest_inList.kt similarity index 87% rename from app/src/test/java/org/thoughtcrime/securesms/service/webrtc/AudioProcessingMethodSelectorTest_modelInList.kt rename to app/src/test/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfigurationTest_inList.kt index 70c16feef2..3f4b5a4a70 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/AudioProcessingMethodSelectorTest_modelInList.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfigurationTest_inList.kt @@ -6,7 +6,7 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) -class AudioProcessingMethodSelectorTest_modelInList( +class RingRtcDynamicConfigurationTest_inList( private val model: String, private val serializedList: String, private val expected: Boolean @@ -14,7 +14,7 @@ class AudioProcessingMethodSelectorTest_modelInList( @Test fun testModelInList() { - val actual = AudioProcessingMethodSelector.modelInList(model, serializedList) + val actual = RingRtcDynamicConfiguration.modelInList(model, serializedList) assertEquals(expected, actual) }