Skip to content

Commit

Permalink
Add telecom integration allow list and change processing for outgoing…
Browse files Browse the repository at this point in the history
… audio calls.
  • Loading branch information
cody-signal committed Jul 21, 2022
1 parent e69d944 commit e024541
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public synchronized boolean callingDisableTelecom() {
if (FeatureFlags.internalUser()) {
return getBoolean(CALLING_DISABLE_TELECOM, false);
} else {
return true;
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ public ActiveCallActionProcessorDelegate(@NonNull WebRtcInteractor webRtcInterac
}

return terminate(currentState, activePeer);
} else {
RemotePeer peerByCallId = currentState.getCallInfoState().getPeerByCallId(callId);
if (peerByCallId != null) {
webRtcInteractor.terminateCall(peerByCallId.getId());
}
}

return currentState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -89,16 +99,18 @@ 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 {
val ourExtras: Bundle = extras.getBundle(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS) ?: extras

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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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()
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
),
)

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public GroupNetworkUnavailableActionProcessor(@NonNull WebRtcInteractor webRtcIn
SignalStore.internalValues().groupCallingServer(),
new byte[0],
null,
AudioProcessingMethodSelector.get(),
RingRtcDynamicConfiguration.getAudioProcessingMethod(),
webRtcInteractor.getGroupCallObserver());

return currentState.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public GroupPreJoinActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
SignalStore.internalValues().groupCallingServer(),
new byte[0],
AUDIO_LEVELS_INTERVAL,
AudioProcessingMethodSelector.get(),
RingRtcDynamicConfiguration.getAudioProcessingMethod(),
webRtcInteractor.getGroupCallObserver());

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public IncomingCallActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
context,
videoState.getLockableEglBase().require(),
AudioProcessingMethodSelector.get(),
RingRtcDynamicConfiguration.getAudioProcessingMethod(),
videoState.requireLocalSink(),
callParticipant.getVideoSink(),
videoState.requireCamera(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public IncomingGroupCallActionProcessor(WebRtcInteractor webRtcInteractor) {
SignalStore.internalValues().groupCallingServer(),
new byte[0],
AUDIO_LEVELS_INTERVAL,
AudioProcessingMethodSelector.get(),
RingRtcDynamicConfiguration.getAudioProcessingMethod(),
webRtcInteractor.getGroupCallObserver());

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public OutgoingCallActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
context,
videoState.getLockableEglBase().require(),
AudioProcessingMethodSelector.get(),
RingRtcDynamicConfiguration.getAudioProcessingMethod(),
videoState.requireLocalSink(),
callParticipant.getVideoSink(),
videoState.requireCamera(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
);

/**
Expand Down Expand Up @@ -477,6 +483,16 @@ public static boolean displayDonorBadges() {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ 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
) {

@Test
fun testModelInList() {
val actual = AudioProcessingMethodSelector.modelInList(model, serializedList)
val actual = RingRtcDynamicConfiguration.modelInList(model, serializedList)
assertEquals(expected, actual)
}

Expand Down

0 comments on commit e024541

Please sign in to comment.