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:
2026-02-20 02:45:00 +05:00
parent 5cf8b2866f
commit 88e2084f8b
26 changed files with 943 additions and 464 deletions

View File

@@ -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
*/