diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/util/CredentialEntryBuilderExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/util/CredentialEntryBuilderExtensions.kt index 5f431b6575..6695ee6ff9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/util/CredentialEntryBuilderExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/util/CredentialEntryBuilderExtensions.kt @@ -7,6 +7,7 @@ import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import com.bitwarden.annotation.OmitFromCoverage import com.bitwarden.core.util.isBuildVersionAtLeast +import com.bitwarden.core.util.isHyperOS import javax.crypto.Cipher /** @@ -15,7 +16,7 @@ import javax.crypto.Cipher fun PublicKeyCredentialEntry.Builder.setBiometricPromptDataIfSupported( cipher: Cipher?, ): PublicKeyCredentialEntry.Builder = - if (isBuildVersionAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) && cipher != null) { + if (isBiometricPromptDataSupported() && cipher != null) { setBiometricPromptData( biometricPromptData = buildPromptDataWithCipher(cipher), ) @@ -29,10 +30,19 @@ fun PublicKeyCredentialEntry.Builder.setBiometricPromptDataIfSupported( fun PasswordCredentialEntry.Builder.setBiometricPromptDataIfSupported( cipher: Cipher?, ): PasswordCredentialEntry.Builder = - if (isBuildVersionAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) && cipher != null) { + if (isBiometricPromptDataSupported() && cipher != null) { setBiometricPromptData( biometricPromptData = buildPromptDataWithCipher(cipher), ) } else { this } + +/** + * Returns whether biometric prompt data is supported on this device. + * Note: Xiaomi HyperOS is known to be incompatible. + */ +private fun isBiometricPromptDataSupported(): Boolean { + return isBuildVersionAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) && + !isHyperOS() +} diff --git a/core/src/main/kotlin/com/bitwarden/core/util/AndroidPropUtils.kt b/core/src/main/kotlin/com/bitwarden/core/util/AndroidPropUtils.kt new file mode 100644 index 0000000000..ee742132a9 --- /dev/null +++ b/core/src/main/kotlin/com/bitwarden/core/util/AndroidPropUtils.kt @@ -0,0 +1,29 @@ +@file:OmitFromCoverage + +package com.bitwarden.core.util + +import com.bitwarden.annotation.OmitFromCoverage + +private const val KEY_XIAOMI_HYPER_OS_NAME = "ro.mi.os.version.name" + +/** + * Returns true if the device is running Xiaomi HyperOS. + */ +fun isHyperOS(): Boolean = !getSystemProperty(KEY_XIAOMI_HYPER_OS_NAME).isNullOrEmpty() + +/** + * Reads an Android system property using the android.os.SystemProperties API + * + * @param key the name of the system property + * @return the property value, or null if unavailable + */ +@Suppress("SameParameterValue", "PrivateApi") +private fun getSystemProperty(key: String): String? { + return try { + val systemProperties = Class.forName("android.os.SystemProperties") + val getMethod = systemProperties.getMethod("get", String::class.java) + getMethod.invoke(null, key) as? String + } catch (_: Throwable) { + null + } +}