diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b583aa5416..c01aed2cca 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -703,6 +703,7 @@ dependencies { implementation(project(":core:ui")) implementation(project(":core:models")) implementation(project(":core:models-jvm")) + implementation(project(":core:serialization")) implementation(project(":feature:camera")) implementation(project(":feature:registration")) implementation(project(":lib:apng")) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt index 68a67d90e3..105ccec039 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -776,6 +776,50 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter } ) + switchPref( + title = DSLSettingsText.from("Set Video Config:"), + isChecked = state.callingSetVideoConfig, + onClick = { + viewModel.setInternalCallingSetVideoConfig(!state.callingSetVideoConfig) + } + ) + + switchPref( + title = DSLSettingsText.from(" Use Hardware Vp9 Encode"), + isChecked = state.callingUseHardwareVp9Encode, + isEnabled = state.callingSetVideoConfig, + onClick = { + viewModel.setInternalCallingUseHardwareVp9Encode(!state.callingUseHardwareVp9Encode) + } + ) + + switchPref( + title = DSLSettingsText.from(" Use Hardware Vp9 Decode"), + isChecked = state.callingUseHardwareVp9Decode, + isEnabled = state.callingSetVideoConfig, + onClick = { + viewModel.setInternalCallingUseHardwareVp9Decode(!state.callingUseHardwareVp9Decode) + } + ) + + switchPref( + title = DSLSettingsText.from(" Use Software Vp9 Encode"), + isChecked = state.callingUseSoftwareVp9Encode, + isEnabled = state.callingSetVideoConfig, + onClick = { + viewModel.setInternalCallingUseSoftwareVp9Encode(!state.callingUseSoftwareVp9Encode) + } + ) + + switchPref( + title = DSLSettingsText.from(" Use Software Vp9 Decode"), + isChecked = state.callingUseSoftwareVp9Decode, + isEnabled = state.callingSetVideoConfig, + onClick = { + viewModel.setInternalCallingUseSoftwareVp9Decode(!state.callingUseSoftwareVp9Decode) + } + ) + dividerPref() // TODO [alex] -- db access on main thread! diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt index 4e6accf7f0..77f8f2bebc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsState.kt @@ -19,6 +19,11 @@ data class InternalSettingsState( val callingUseSoftwareNs: Boolean, val callingUseInputLowLatency: Boolean, val callingUseInputVoiceComm: Boolean, + val callingSetVideoConfig: Boolean, + val callingUseHardwareVp9Encode: Boolean, + val callingUseHardwareVp9Decode: Boolean, + val callingUseSoftwareVp9Encode: Boolean, + val callingUseSoftwareVp9Decode: Boolean, val useBuiltInEmojiSet: Boolean, val emojiVersion: EmojiFiles.Version?, val removeSenderKeyMinimium: Boolean, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt index e705247473..f75af3c050 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsViewModel.kt @@ -142,6 +142,31 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito refresh() } + fun setInternalCallingSetVideoConfig(enabled: Boolean) { + preferenceDataStore.putBoolean(InternalValues.CALLING_SET_VIDEO_CONFIG, enabled) + refresh() + } + + fun setInternalCallingUseHardwareVp9Encode(enabled: Boolean) { + preferenceDataStore.putBoolean(InternalValues.CALLING_USE_HARDWARE_VP9_ENCODE, enabled) + refresh() + } + + fun setInternalCallingUseHardwareVp9Decode(enabled: Boolean) { + preferenceDataStore.putBoolean(InternalValues.CALLING_USE_HARDWARE_VP9_DECODE, enabled) + refresh() + } + + fun setInternalCallingUseSoftwareVp9Encode(enabled: Boolean) { + preferenceDataStore.putBoolean(InternalValues.CALLING_USE_SOFTWARE_VP9_ENCODE, enabled) + refresh() + } + + fun setInternalCallingUseSoftwareVp9Decode(enabled: Boolean) { + preferenceDataStore.putBoolean(InternalValues.CALLING_USE_SOFTWARE_VP9_DECODE, enabled) + refresh() + } + fun setUseConversationItemV2Media(enabled: Boolean) { SignalStore.internal.useConversationItemV2Media = enabled refresh() @@ -228,6 +253,11 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito callingUseSoftwareNs = SignalStore.internal.callingUseSoftwareNs, callingUseInputLowLatency = SignalStore.internal.callingUseInputLowLatency, callingUseInputVoiceComm = SignalStore.internal.callingUseInputVoiceComm, + callingSetVideoConfig = SignalStore.internal.callingSetVideoConfig, + callingUseHardwareVp9Encode = SignalStore.internal.callingUseHardwareVp9Encode, + callingUseHardwareVp9Decode = SignalStore.internal.callingUseHardwareVp9Decode, + callingUseSoftwareVp9Encode = SignalStore.internal.callingUseSoftwareVp9Encode, + callingUseSoftwareVp9Decode = SignalStore.internal.callingUseSoftwareVp9Decode, useBuiltInEmojiSet = SignalStore.internal.forceBuiltInEmoji, emojiVersion = null, removeSenderKeyMinimium = SignalStore.internal.removeSenderKeyMinimum, diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt index b3c4824e43..574a58f530 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/InternalValues.kt @@ -25,6 +25,11 @@ class InternalValues internal constructor(store: KeyValueStore) : SignalStoreVal const val CALLING_USE_SOFTWARE_NS: String = "internal.calling_use_software_ns" const val CALLING_USE_INPUT_LOW_LATENCY: String = "internal.calling_use_input_low_latency" const val CALLING_USE_INPUT_VOICE_COMM: String = "internal.calling_use_input_voice_comm" + const val CALLING_SET_VIDEO_CONFIG: String = "internal.calling_set_video_config" + const val CALLING_USE_HARDWARE_VP9_ENCODE: String = "internal.calling_use_hardware_vp9_encode" + const val CALLING_USE_HARDWARE_VP9_DECODE: String = "internal.calling_use_hardware_vp9_decode" + const val CALLING_USE_SOFTWARE_VP9_ENCODE: String = "internal.calling_use_software_vp9_encode" + const val CALLING_USE_SOFTWARE_VP9_DECODE: String = "internal.calling_use_software_vp9_decode" const val SHAKE_TO_REPORT: String = "internal.shake_to_report" const val DISABLE_STORAGE_SERVICE: String = "internal.disable_storage_service" const val LAST_SCROLL_POSITION: String = "internal.last_scroll_position" @@ -170,6 +175,31 @@ class InternalValues internal constructor(store: KeyValueStore) : SignalStoreVal */ var callingUseInputVoiceComm by booleanValue(CALLING_USE_INPUT_VOICE_COMM, true).defaultForExternalUsers() + /** + * Whether or not to override the video settings from the remote configuration. + */ + var callingSetVideoConfig by booleanValue(CALLING_SET_VIDEO_CONFIG, false).falseForExternalUsers() + + /** + * If overriding the video settings, use hardware VP9 encoder or not if available + */ + var callingUseHardwareVp9Encode by booleanValue(CALLING_USE_HARDWARE_VP9_ENCODE, true).defaultForExternalUsers() + + /** + * If overriding the video settings, use hardware VP9 decoder or not if available + */ + var callingUseHardwareVp9Decode by booleanValue(CALLING_USE_HARDWARE_VP9_DECODE, true).defaultForExternalUsers() + + /** + * If overriding the video settings, use software VP9 encoder or not + */ + var callingUseSoftwareVp9Encode by booleanValue(CALLING_USE_SOFTWARE_VP9_ENCODE, true).defaultForExternalUsers() + + /** + * If overriding the video settings, use software VP9 encoder or not + */ + var callingUseSoftwareVp9Decode by booleanValue(CALLING_USE_SOFTWARE_VP9_DECODE, true).defaultForExternalUsers() + var lastScrollPosition: Int by integerValue(LAST_SCROLL_POSITION, 0).defaultForExternalUsers() var useConversationItemV2Media by booleanValue(CONVERSATION_ITEM_V2_MEDIA, false).defaultForExternalUsers() 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 29e0b49a2e..777fbe5de4 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 @@ -98,7 +98,6 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { } byte dredDuration = (byte) RemoteConfig.dredDuration(); - boolean enableVp9 = RemoteConfig.enableSoftwareVp9(); boolean hideIp = !activePeer.getRecipient().isProfileSharing() || callSetupState.isAlwaysTurnServers(); VideoState videoState = currentState.getVideoState(); CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient())); @@ -108,6 +107,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { context, videoState.getLockableEglBase().require(), RingRtcDynamicConfiguration.getAudioConfig(), + RingRtcDynamicConfiguration.getVideoConfig(), videoState.requireLocalSink(), callParticipant.getVideoSink(), videoState.requireRouter(), @@ -116,7 +116,6 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { NetworkUtil.getCallingDataMode(context), AUDIO_LEVELS_INTERVAL, dredDuration, - enableVp9, false); } catch (CallException e) { return callFailure(currentState, "Unable to proceed with call: ", e); 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 9f497e935d..96abb7aeb2 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 @@ -147,7 +147,6 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { } byte dredDuration = (byte) RemoteConfig.dredDuration(); - boolean enableVp9 = RemoteConfig.enableSoftwareVp9(); boolean hideIp = !activePeer.getRecipient().isProfileSharing() || callSetupState.isAlwaysTurnServers(); VideoState videoState = currentState.getVideoState(); CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient())); @@ -157,6 +156,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { context, videoState.getLockableEglBase().require(), RingRtcDynamicConfiguration.getAudioConfig(), + RingRtcDynamicConfiguration.getVideoConfig(), videoState.requireLocalSink(), callParticipant.getVideoSink(), videoState.requireRouter(), @@ -165,7 +165,6 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { NetworkUtil.getCallingDataMode(context), AUDIO_LEVELS_INTERVAL, dredDuration, - enableVp9, currentState.getCallSetupState(activePeer).isEnableVideoOnCreate()); } catch (CallException e) { return callFailure(currentState, "Unable to proceed with call: ", e); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfiguration.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfiguration.kt index 620809e5a3..0335edafb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfiguration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/RingRtcDynamicConfiguration.kt @@ -2,10 +2,13 @@ package org.thoughtcrime.securesms.service.webrtc import android.os.Build import org.signal.core.util.asListContains +import org.signal.core.util.isNotNullOrBlank import org.signal.ringrtc.AudioConfig +import org.signal.ringrtc.VideoConfig import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.webrtc.audio.AudioDeviceConfig +import java.io.File /** * Utility class to determine the audio configuration that RingRTC should use. @@ -40,4 +43,65 @@ object RingRtcDynamicConfiguration { } return AudioDeviceConfig.getCurrentConfig() } + + @JvmStatic + fun getVideoConfig(): VideoConfig { + if (RemoteConfig.internalUser && SignalStore.internal.callingSetVideoConfig) { + val videoConfig = VideoConfig() + videoConfig.enableHardwareVp9Encode = SignalStore.internal.callingUseHardwareVp9Encode + videoConfig.enableHardwareVp9Decode = SignalStore.internal.callingUseHardwareVp9Decode + videoConfig.enableSoftwareVp9Encode = SignalStore.internal.callingUseSoftwareVp9Encode + videoConfig.enableSoftwareVp9Decode = SignalStore.internal.callingUseSoftwareVp9Decode + return videoConfig + } + + val (manufacturer, model) = getSoCInfo() + val soc = "${manufacturer.lowercase()}-${model.lowercase()}" + val videoConfig = VideoConfig() + videoConfig.enableHardwareVp9Encode = !RemoteConfig.disableHardwareVp9EncodeSocList.contains(soc) && !RemoteConfig.disableHardwareVp9EncodeSocList.contains(model) + videoConfig.enableHardwareVp9Decode = !RemoteConfig.disableHardwareVp9DecodeSocList.contains(soc) && !RemoteConfig.disableHardwareVp9DecodeSocList.contains(model) + videoConfig.enableSoftwareVp9Encode = RemoteConfig.enableSoftwareVp9EncodeSoCList.contains(soc) || RemoteConfig.enableSoftwareVp9EncodeSoCList.contains(model) + videoConfig.enableSoftwareVp9Decode = RemoteConfig.enableSoftwareVp9Decode || RemoteConfig.enableSoftwareVp9DecodeSoCList.contains(soc) || RemoteConfig.enableSoftwareVp9DecodeSoCList.contains(model) + + return videoConfig + } + + private fun getSoCInfo(): Pair { + // 1. Native API Method (Android 12 / API 31+) + if (Build.VERSION.SDK_INT >= 31) { + return Build.SOC_MANUFACTURER to Build.SOC_MODEL + } else { + // 2. Fallback Method for Older Devices (Android 11 and below) + val socModel = getSystemProperty("ro.board.platform").takeIf { it.isNotNullOrBlank() && !it.equals("unknown", ignoreCase = true) } + ?: parseCpuInfoForHardware().takeIf { it.isNotNullOrBlank() } + ?: "unknown" + return "unknown" to socModel + } + } + + private fun getSystemProperty(key: String): String { + return try { + val systemProperties = Class.forName("android.os.SystemProperties") + val get = systemProperties.getMethod("get", String::class.java) + get.invoke(null, key) as String + } catch (_: Exception) { + "" + } + } + + /** + * Helper to parse /proc/cpuinfo for the "Hardware" field + */ + private fun parseCpuInfoForHardware(): String { + return try { + File("/proc/cpuinfo").useLines { lines -> + lines.firstOrNull { it.startsWith("Hardware", ignoreCase = true) } + ?.substringAfter(':', "") + ?.trim() + ?: "" + } + } catch (_: Exception) { + "" + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt index e69755135b..73deeb06e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt @@ -11,6 +11,7 @@ import org.signal.core.util.gibiBytes import org.signal.core.util.kibiBytes import org.signal.core.util.logging.Log import org.signal.core.util.mebiBytes +import org.signal.core.util.serialization.SignalJson import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.groups.SelectionLimits import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob @@ -545,6 +546,25 @@ object RemoteConfig { ) } + private fun remoteStringSet( + key: String, + defaultValue: Set, + hotSwappable: Boolean, + active: Boolean = true, + onChangeListener: OnFlagChange? = null + ): Config> { + return remoteValue( + key = key, + hotSwappable = hotSwappable, + sticky = false, + active = active, + onChangeListener = onChangeListener, + transformer = { value -> + value?.let { SignalJson.decode>(it.toString()).getOrNull() } ?: defaultValue + } + ) + } + private fun remoteValue( key: String, hotSwappable: Boolean, @@ -1309,12 +1329,69 @@ object RemoteConfig { ) /** - * Enables software Vp9 support for 1:1 calls + * Enables software Vp9 encode support for 1:1 calls + * Contains SoCs that are capable of encoding VP9 */ @JvmStatic - @get:JvmName("enableSoftwareVp9") - val enableSoftwareVp9: Boolean by remoteBoolean( - key = "android.calling.enableSoftwareVp9", + @get:JvmName("enableSoftwareVp9EncodeSoCList") + val enableSoftwareVp9EncodeSoCList: Set by remoteStringSet( + key = "android.calling.enableSoftwareVp9EncodeSocList", + defaultValue = setOf(), + hotSwappable = true + ) + + /** + * Enables software Vp9 decode support for 1:1 calls + * Contains SoCs that are capable of decoding VP9 + */ + @JvmStatic + @get:JvmName("enableSoftwareVp9DecodeSoCList") + val enableSoftwareVp9DecodeSoCList: Set by remoteStringSet( + key = "android.calling.enableSoftwareVp9DecodeSoCList", + defaultValue = setOf(), + hotSwappable = true + ) + + /** + * Enables software Vp9 decode support for 1:1 calls for all devices + */ + @JvmStatic + @get:JvmName("enableSoftwareVp9Decode") + val enableSoftwareVp9Decode: Boolean by remoteBoolean( + key = "android.calling.enableSoftwareVp9Decode", + defaultValue = false, + hotSwappable = true + ) + + /** + * List of devices to skip hardware VP9 on due to reliability issues + */ + @JvmStatic + @get:JvmName("disableHardwareVp9EncodeSocList") + val disableHardwareVp9EncodeSocList: Set by remoteStringSet( + key = "android.calling.disableHardwareVp9EncodeSocList", + defaultValue = setOf(), + hotSwappable = true + ) + + /** + * List of devices to skip hardware VP9 on due to reliability issues + */ + @JvmStatic + @get:JvmName("disableHardwareVp9DecodeSocList") + val disableHardwareVp9DecodeSocList: Set by remoteStringSet( + key = "android.calling.disableHardwareVp9DecodeSocList", + defaultValue = setOf(), + hotSwappable = true + ) + + /** + * Enables using VP9 in Group Calls + */ + @JvmStatic + @get:JvmName("enableGroupCallVp9") + val enableGroupCallVp9: Boolean by remoteBoolean( + key = "android.calling.enableGroupCallVp9", defaultValue = false, hotSwappable = true ) diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/RemoteConfig_StaticValuesTest.kt b/app/src/test/java/org/thoughtcrime/securesms/util/RemoteConfig_StaticValuesTest.kt index 93b1071ef3..617a25d5bd 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/util/RemoteConfig_StaticValuesTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/util/RemoteConfig_StaticValuesTest.kt @@ -37,7 +37,9 @@ class RemoteConfig_StaticValuesTest { "100", "12345678910111213141516", "*", - "1.0.0" + "1.0.0", + "[]", + "[\"foo\"]" ) val configKeys = RemoteConfig.configsByKey.keys diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d2e422d7b..be9f9a9270 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -171,7 +171,7 @@ libsignal-client = { module = "org.signal:libsignal-client", version.ref = "libs libsignal-android = { module = "org.signal:libsignal-android", version.ref = "libsignal-client" } protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobuf-gradle-plugin" } signal-aesgcmprovider = "org.signal:aesgcmprovider:0.0.4" -signal-ringrtc = "org.signal:ringrtc-android:2.69.7" +signal-ringrtc = "org.signal:ringrtc-android:2.70.0" # Third Party signal-android-database-sqlcipher = "net.zetetic:sqlcipher-android:4.17.0" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 000ac9a881..87c8ab8776 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -8307,12 +8307,12 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + +