Refactor image handling and decoding logic
- Introduced a maximum bitmap decode dimension to prevent excessive memory usage. - Enhanced base64 to bitmap conversion by extracting payload and applying EXIF orientation. - Improved error handling for image downloads and decoding processes. - Simplified media picker and chat input components to manage keyboard visibility more effectively. - Updated color selection grid to adaptively adjust based on available width. - Added safety checks for notifications and call actions in profile screens. - Optimized bitmap decoding in uriToBase64Image to handle large images more efficiently.
This commit is contained in:
@@ -133,7 +133,7 @@ class BiometricAuthManager(private val context: Context) {
|
||||
removeBiometricData()
|
||||
onError("Биометрические данные изменились. Пожалуйста, настройте заново.")
|
||||
} catch (e: Exception) {
|
||||
onError("Ошибка инициализации: ${e.message}")
|
||||
onError("Ошибка инициализации: ${formatInitializationError(e)}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ class BiometricAuthManager(private val context: Context) {
|
||||
removeBiometricData()
|
||||
onError("Биометрические данные изменились. Пожалуйста, войдите с паролем и настройте биометрию заново.")
|
||||
} catch (e: Exception) {
|
||||
onError("Ошибка инициализации: ${e.message}")
|
||||
onError("Ошибка инициализации: ${formatInitializationError(e)}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,13 +264,68 @@ class BiometricAuthManager(private val context: Context) {
|
||||
private fun getOrCreateSecretKey(): SecretKey {
|
||||
// Проверяем, есть ли уже ключ
|
||||
getSecretKey()?.let { return it }
|
||||
|
||||
// Генерируем новый ключ в Keystore
|
||||
val keyGenerator = KeyGenerator.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_AES,
|
||||
KEYSTORE_PROVIDER
|
||||
|
||||
val attempts = buildKeyGenerationAttempts()
|
||||
var lastError: Exception? = null
|
||||
|
||||
for ((index, config) in attempts.withIndex()) {
|
||||
try {
|
||||
val keyGenerator = KeyGenerator.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_AES,
|
||||
KEYSTORE_PROVIDER
|
||||
)
|
||||
val spec = buildKeyGenSpec(config)
|
||||
keyGenerator.init(spec)
|
||||
return keyGenerator.generateKey()
|
||||
} catch (e: Exception) {
|
||||
lastError = e
|
||||
Log.w(
|
||||
TAG,
|
||||
"Key generation attempt ${index + 1}/${attempts.size} failed " +
|
||||
"(strongBox=${config.useStrongBox}, attestation=${config.useAttestation}, " +
|
||||
"unlockedRequired=${config.requireUnlockedDevice})",
|
||||
e
|
||||
)
|
||||
removeKeyAliasSilently()
|
||||
}
|
||||
}
|
||||
|
||||
throw IllegalStateException("Failed to generate key", lastError)
|
||||
}
|
||||
|
||||
private fun buildKeyGenerationAttempts(): List<KeyGenerationConfig> {
|
||||
val attempts = mutableListOf<KeyGenerationConfig>()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
attempts += KeyGenerationConfig(
|
||||
useStrongBox = true,
|
||||
useAttestation = true,
|
||||
requireUnlockedDevice = true
|
||||
)
|
||||
}
|
||||
|
||||
attempts += KeyGenerationConfig(
|
||||
useStrongBox = false,
|
||||
useAttestation = true,
|
||||
requireUnlockedDevice = true
|
||||
)
|
||||
|
||||
|
||||
attempts += KeyGenerationConfig(
|
||||
useStrongBox = false,
|
||||
useAttestation = false,
|
||||
requireUnlockedDevice = true
|
||||
)
|
||||
|
||||
attempts += KeyGenerationConfig(
|
||||
useStrongBox = false,
|
||||
useAttestation = false,
|
||||
requireUnlockedDevice = false
|
||||
)
|
||||
|
||||
return attempts
|
||||
}
|
||||
|
||||
private fun buildKeyGenSpec(config: KeyGenerationConfig): KeyGenParameterSpec {
|
||||
val builder = KeyGenParameterSpec.Builder(
|
||||
KEY_ALIAS,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
||||
@@ -278,42 +333,49 @@ class BiometricAuthManager(private val context: Context) {
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(256)
|
||||
// Ключ доступен только после биометрической аутентификации
|
||||
.setUserAuthenticationRequired(true)
|
||||
// Ключ инвалидируется при добавлении новой биометрии
|
||||
.setInvalidatedByBiometricEnrollment(true)
|
||||
|
||||
// Ключ доступен только когда устройство разблокировано
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
|
||||
if (config.requireUnlockedDevice && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setUnlockedDeviceRequired(true)
|
||||
}
|
||||
|
||||
// Для Android 11+ устанавливаем параметры аутентификации
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
builder.setUserAuthenticationParameters(
|
||||
0, // timeout = 0 означает требование аутентификации для каждой операции
|
||||
0,
|
||||
KeyProperties.AUTH_BIOMETRIC_STRONG
|
||||
)
|
||||
}
|
||||
|
||||
// Включаем Key Attestation для проверки TEE/StrongBox
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
try {
|
||||
builder.setAttestationChallenge(generateAttestationChallenge())
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
if (config.useAttestation && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
builder.setAttestationChallenge(generateAttestationChallenge())
|
||||
}
|
||||
|
||||
// Используем StrongBox если доступен (аппаратный модуль безопасности)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
try {
|
||||
builder.setIsStrongBoxBacked(true)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
if (config.useStrongBox && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
builder.setIsStrongBoxBacked(true)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun removeKeyAliasSilently() {
|
||||
try {
|
||||
if (keyStore.containsAlias(KEY_ALIAS)) {
|
||||
keyStore.deleteEntry(KEY_ALIAS)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatInitializationError(error: Throwable): String {
|
||||
val rootCause = generateSequence(error) { it.cause }.lastOrNull() ?: error
|
||||
val message = rootCause.message ?: error.message ?: "Unknown error"
|
||||
|
||||
return if (message.contains("Failed to generate key", ignoreCase = true)) {
|
||||
"Не удалось создать ключ. Проверьте, что на устройстве включена блокировка экрана и настроена биометрия."
|
||||
} else {
|
||||
message
|
||||
}
|
||||
|
||||
keyGenerator.init(builder.build())
|
||||
return keyGenerator.generateKey()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -483,6 +545,12 @@ class BiometricAuthManager(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private data class KeyGenerationConfig(
|
||||
val useStrongBox: Boolean,
|
||||
val useAttestation: Boolean,
|
||||
val requireUnlockedDevice: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* Результат проверки Key Attestation
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user