Add telecom integration allow list and change processing for outgoing audio calls.

This commit is contained in:
Cody Henthorne
2022-07-18 11:17:16 -04:00
parent e69d944f11
commit e024541b8a
13 changed files with 86 additions and 24 deletions

View File

@@ -187,7 +187,7 @@ public final class InternalValues extends SignalStoreValues {
if (FeatureFlags.internalUser()) {
return getBoolean(CALLING_DISABLE_TELECOM, false);
} else {
return true;
return false;
}
}
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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()
}
}

View File

@@ -47,7 +47,7 @@ class GroupNetworkUnavailableActionProcessor extends WebRtcActionProcessor {
SignalStore.internalValues().groupCallingServer(),
new byte[0],
null,
AudioProcessingMethodSelector.get(),
RingRtcDynamicConfiguration.getAudioProcessingMethod(),
webRtcInteractor.getGroupCallObserver());
return currentState.builder()

View File

@@ -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 {

View File

@@ -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(),

View File

@@ -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 {

View File

@@ -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(),

View File

@@ -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())
}

View File

@@ -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);