mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-22 18:55:12 +00:00
Add telecom integration allow list and change processing for outgoing audio calls.
This commit is contained in:
@@ -187,7 +187,7 @@ public final class InternalValues extends SignalStoreValues {
|
||||
if (FeatureFlags.internalUser()) {
|
||||
return getBoolean(CALLING_DISABLE_TELECOM, false);
|
||||
} else {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class GroupNetworkUnavailableActionProcessor extends WebRtcActionProcessor {
|
||||
SignalStore.internalValues().groupCallingServer(),
|
||||
new byte[0],
|
||||
null,
|
||||
AudioProcessingMethodSelector.get(),
|
||||
RingRtcDynamicConfiguration.getAudioProcessingMethod(),
|
||||
webRtcInteractor.getGroupCallObserver());
|
||||
|
||||
return currentState.builder()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user