fix: Biometric error prompts #2081

This commit is contained in:
J-Jamet
2025-07-24 16:39:42 +02:00
parent 56f8a1bf9f
commit 593b5c6338
4 changed files with 231 additions and 87 deletions

View File

@@ -32,7 +32,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.CompoundButton
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
@@ -43,6 +42,9 @@ import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
@@ -81,10 +83,11 @@ import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import kotlinx.coroutines.launch
import java.io.FileNotFoundException import java.io.FileNotFoundException
class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener { class MainCredentialActivity : DatabaseModeActivity() {
// Views // Views
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
@@ -166,21 +169,13 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
} }
// Listen password checkbox to init advanced unlock and confirmation button // Listen password checkbox to init advanced unlock and confirmation button
mainCredentialView?.onPasswordChecked = mainCredentialView?.onConditionToStoreCredentialChanged = { credentialStorage, verified ->
CompoundButton.OnCheckedChangeListener { _, _ -> mAdvancedUnlockViewModel.checkUnlockAvailability(
mAdvancedUnlockViewModel.checkUnlockAvailability() conditionToStoreCredentialVerified = verified
enableConfirmationButton() )
} // TODO Async by ViewModel
mainCredentialView?.onKeyFileChecked = enableConfirmationButton()
CompoundButton.OnCheckedChangeListener { _, _ -> }
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
mainCredentialView?.onHardwareKeyChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
// Observe if default database // Observe if default database
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase -> mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
@@ -228,6 +223,27 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri, hardwareKey) onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri, hardwareKey)
} }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mAdvancedUnlockViewModel.uiState.collect { uiState ->
// New value received
if (uiState.isCredentialRequired) {
mAdvancedUnlockViewModel.provideCredentialForEncryption(
getCredentialForEncryption()
)
}
uiState.cipherEncryptDatabase?.let { cipherEncryptDatabase ->
onCredentialEncrypted(cipherEncryptDatabase)
mAdvancedUnlockViewModel.consumeCredentialEncrypted()
}
uiState.cipherDecryptDatabase?.let { cipherDecryptDatabase ->
onCredentialDecrypted(cipherDecryptDatabase)
mAdvancedUnlockViewModel.consumeCredentialDecrypted()
}
}
}
}
} }
override fun onResume() { override fun onResume() {
@@ -400,23 +416,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
} }
} }
override fun retrieveCredentialForEncryption(): ByteArray {
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
?: byteArrayOf()
}
override fun conditionToStoreCredential(): Boolean {
return mainCredentialView?.conditionToStoreCredential() == true
}
override fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
// Load the database if password is registered with biometric
loadDatabase(mDatabaseFileUri,
mainCredentialView?.getMainCredential(),
cipherEncryptDatabase
)
}
private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener { private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener {
override fun passwordToStore(password: String?): ByteArray? { override fun passwordToStore(password: String?): ByteArray? {
return password?.toByteArray() return password?.toByteArray()
@@ -433,7 +432,20 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
} }
} }
override fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) { private fun getCredentialForEncryption(): ByteArray {
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
?: byteArrayOf()
}
private fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
// Load the database if password is registered with biometric
loadDatabase(mDatabaseFileUri,
mainCredentialView?.getMainCredential(),
cipherEncryptDatabase
)
}
private fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
// Load the database if password is retrieve from biometric // Load the database if password is retrieve from biometric
// Retrieve from biometric // Retrieve from biometric
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential() val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()

View File

@@ -35,7 +35,9 @@ import androidx.biometric.BiometricPrompt
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseAction import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
@@ -52,8 +54,6 @@ import kotlinx.coroutines.launch
class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCallback { class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
private var mBuilderListener: BuilderListener? = null
private var mAdvancedUnlockEnabled = false private var mAdvancedUnlockEnabled = false
private var mAutoOpenPromptEnabled = false private var mAutoOpenPromptEnabled = false
@@ -84,6 +84,8 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
// Only keep connection when we request a device credential activity // Only keep connection when we request a device credential activity
private var keepConnection = false private var keepConnection = false
private var isConditionToStoreCredentialVerified = false
private var mDeviceCredentialResultLauncher = registerForActivityResult( private var mDeviceCredentialResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
@@ -120,14 +122,6 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context) mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context)
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context) mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBuilderListener = context as BuilderListener
}
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + BuilderListener::class.java.name)
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -138,11 +132,6 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) { mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) {
initAdvancedUnlockMode() initAdvancedUnlockMode()
} }
mAdvancedUnlockViewModel.onUnlockAvailabilityCheckRequested.observe(this) {
checkUnlockAvailability()
}
mAdvancedUnlockViewModel.onDatabaseFileLoaded.observe(this) { mAdvancedUnlockViewModel.onDatabaseFileLoaded.observe(this) {
onDatabaseLoaded(it) onDatabaseLoaded(it)
} }
@@ -162,6 +151,27 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity?.addMenuProvider(menuProvider, viewLifecycleOwner) activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
mAdvancedUnlockViewModel.uiState.collect { uiState ->
// New credential value received
uiState.credential?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockManager?.encryptData(uiState.credential)
}
mAdvancedUnlockViewModel.consumeCredentialForEncryption()
}
// Condition to store credential verified
isConditionToStoreCredentialVerified = uiState.isConditionToStoreCredentialVerified
// Check unlock availability
if (uiState.onUnlockAvailabilityCheckRequested) {
checkUnlockAvailability()
mAdvancedUnlockViewModel.consumeCheckUnlockAvailability()
}
}
}
}
} }
override fun onResume() { override fun onResume() {
@@ -250,7 +260,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
if (advancedUnlockManager?.isKeyManagerInitialized != true) { if (advancedUnlockManager?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE) toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else { } else {
if (mBuilderListener?.conditionToStoreCredential() == true) { if (isConditionToStoreCredentialVerified) {
// listen for encryption // listen for encryption
toggleMode(Mode.STORE_CREDENTIAL) toggleMode(Mode.STORE_CREDENTIAL)
} else { } else {
@@ -261,8 +271,13 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
// listen for decryption // listen for decryption
Mode.EXTRACT_CREDENTIAL Mode.EXTRACT_CREDENTIAL
} else { } else {
// wait for typing if (isConditionToStoreCredentialVerified) {
Mode.WAIT_CREDENTIAL // if condition OK, key manager in error
Mode.KEY_MANAGER_UNAVAILABLE
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
}
}) })
} }
} }
@@ -523,9 +538,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
} }
Mode.STORE_CREDENTIAL -> { Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way // newly store the entered password in encrypted way
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential -> mAdvancedUnlockViewModel.retrieveCredentialForEncryption()
advancedUnlockManager?.encryptData(credential)
}
} }
Mode.EXTRACT_CREDENTIAL -> { Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences // retrieve the encrypted value from preferences
@@ -545,7 +558,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) { override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {
databaseFileUri?.let { databaseUri -> databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialEncrypted( mAdvancedUnlockViewModel.onCredentialEncrypted(
CipherEncryptDatabase().apply { CipherEncryptDatabase().apply {
this.databaseUri = databaseUri this.databaseUri = databaseUri
this.credentialStorage = credentialDatabaseStorage this.credentialStorage = credentialDatabaseStorage
@@ -559,7 +572,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
override fun handleDecryptedResult(decryptedValue: ByteArray) { override fun handleDecryptedResult(decryptedValue: ByteArray) {
// Load database directly with password retrieve // Load database directly with password retrieve
databaseFileUri?.let { databaseUri -> databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialDecrypted( mAdvancedUnlockViewModel.onCredentialDecrypted(
CipherDecryptDatabase().apply { CipherDecryptDatabase().apply {
this.databaseUri = databaseUri this.databaseUri = databaseUri
this.credentialStorage = credentialDatabaseStorage this.credentialStorage = credentialDatabaseStorage
@@ -630,13 +643,6 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
EXTRACT_CREDENTIAL EXTRACT_CREDENTIAL
} }
interface BuilderListener {
fun retrieveCredentialForEncryption(): ByteArray
fun conditionToStoreCredential(): Boolean
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase)
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase)
}
override fun onPause() { override fun onPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!keepConnection) { if (!keepConnection) {
@@ -645,13 +651,11 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
advancedUnlockManager = null advancedUnlockManager = null
} }
} }
super.onPause() super.onPause()
} }
override fun onDestroyView() { override fun onDestroyView() {
mAdvancedUnlockInfoView = null mAdvancedUnlockInfoView = null
super.onDestroyView() super.onDestroyView()
} }
@@ -659,20 +663,12 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
disconnect() disconnect()
advancedUnlockManager = null advancedUnlockManager = null
mBuilderListener = null
} }
mAdvancedUnlockViewModel.deleteData()
super.onDestroy() super.onDestroy()
} }
override fun onDetach() {
mBuilderListener = null
super.onDetach()
}
companion object { companion object {
private val TAG = AdvancedUnlockFragment::class.java.name private val TAG = AdvancedUnlockFragment::class.java.name
} }
} }

View File

@@ -53,9 +53,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
private var checkboxHardwareView: CompoundButton private var checkboxHardwareView: CompoundButton
private var hardwareKeySelectionView: HardwareKeySelectionView private var hardwareKeySelectionView: HardwareKeySelectionView
var onPasswordChecked: (CompoundButton.OnCheckedChangeListener)? = null var onConditionToStoreCredentialChanged: ((CredentialStorage, verified: Boolean) -> Unit)? = null
var onKeyFileChecked: (CompoundButton.OnCheckedChangeListener)? = null
var onHardwareKeyChecked: (CompoundButton.OnCheckedChangeListener)? = null
var onValidateListener: (() -> Unit)? = null var onValidateListener: (() -> Unit)? = null
private var mCredentialStorage: CredentialStorage = CredentialStorage.PASSWORD private var mCredentialStorage: CredentialStorage = CredentialStorage.PASSWORD
@@ -104,7 +102,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
} }
checkboxPasswordView.setOnCheckedChangeListener { view, checked -> checkboxPasswordView.setOnCheckedChangeListener { view, checked ->
onPasswordChecked?.onCheckedChanged(view, checked) onConditionToStoreCredentialChanged?.invoke(
mCredentialStorage,
conditionToStoreCredential()
)
} }
checkboxKeyFileView.setOnCheckedChangeListener { view, checked -> checkboxKeyFileView.setOnCheckedChangeListener { view, checked ->
if (checked) { if (checked) {
@@ -112,7 +113,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
checkboxKeyFileView.isChecked = false checkboxKeyFileView.isChecked = false
} }
} }
onKeyFileChecked?.onCheckedChanged(view, checked) onConditionToStoreCredentialChanged?.invoke(
mCredentialStorage,
conditionToStoreCredential()
)
} }
checkboxHardwareView.setOnCheckedChangeListener { view, checked -> checkboxHardwareView.setOnCheckedChangeListener { view, checked ->
if (checked) { if (checked) {
@@ -120,7 +124,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
checkboxHardwareView.isChecked = false checkboxHardwareView.isChecked = false
} }
} }
onHardwareKeyChecked?.onCheckedChanged(view, checked) onConditionToStoreCredentialChanged?.invoke(
mCredentialStorage,
conditionToStoreCredential()
)
} }
hardwareKeySelectionView.selectionListener = { _ -> hardwareKeySelectionView.selectionListener = { _ ->

View File

@@ -3,18 +3,23 @@ package com.kunzisoft.keepass.viewmodels
import android.net.Uri import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.model.CipherDecryptDatabase
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
class AdvancedUnlockViewModel : ViewModel() { class AdvancedUnlockViewModel : ViewModel() {
var allowAutoOpenBiometricPrompt : Boolean = true var allowAutoOpenBiometricPrompt : Boolean = true
var deviceCredentialAuthSucceeded: Boolean? = null var deviceCredentialAuthSucceeded: Boolean? = null
private val _uiState = MutableStateFlow(DeviceUnlockUiStates())
val uiState: StateFlow<DeviceUnlockUiStates> = _uiState
val onInitAdvancedUnlockModeRequested : LiveData<Void?> get() = _onInitAdvancedUnlockModeRequested val onInitAdvancedUnlockModeRequested : LiveData<Void?> get() = _onInitAdvancedUnlockModeRequested
private val _onInitAdvancedUnlockModeRequested = SingleLiveEvent<Void?>() private val _onInitAdvancedUnlockModeRequested = SingleLiveEvent<Void?>()
val onUnlockAvailabilityCheckRequested : LiveData<Void?> get() = _onUnlockAvailabilityCheckRequested
private val _onUnlockAvailabilityCheckRequested = SingleLiveEvent<Void?>()
val onDatabaseFileLoaded : LiveData<Uri?> get() = _onDatabaseFileLoaded val onDatabaseFileLoaded : LiveData<Uri?> get() = _onDatabaseFileLoaded
private val _onDatabaseFileLoaded = SingleLiveEvent<Uri?>() private val _onDatabaseFileLoaded = SingleLiveEvent<Uri?>()
@@ -22,11 +27,135 @@ class AdvancedUnlockViewModel : ViewModel() {
_onInitAdvancedUnlockModeRequested.call() _onInitAdvancedUnlockModeRequested.call()
} }
fun checkUnlockAvailability() { fun checkUnlockAvailability(conditionToStoreCredentialVerified: Boolean) {
_onUnlockAvailabilityCheckRequested.call() _uiState.update { currentState ->
currentState.copy(
onUnlockAvailabilityCheckRequested = true,
isConditionToStoreCredentialVerified = conditionToStoreCredentialVerified
)
}
}
fun consumeCheckUnlockAvailability() {
_uiState.update { currentState ->
currentState.copy(
onUnlockAvailabilityCheckRequested = false
)
}
} }
fun databaseFileLoaded(databaseUri: Uri?) { fun databaseFileLoaded(databaseUri: Uri?) {
_onDatabaseFileLoaded.value = databaseUri _onDatabaseFileLoaded.value = databaseUri
} }
fun retrieveCredentialForEncryption() {
_uiState.update { currentState ->
currentState.copy(
isCredentialRequired = true,
credential = null
)
}
}
fun provideCredentialForEncryption(credential: ByteArray) {
_uiState.update { currentState ->
currentState.copy(
isCredentialRequired = false,
credential = credential
)
}
}
fun consumeCredentialForEncryption() {
_uiState.update { currentState ->
currentState.copy(
isCredentialRequired = false,
credential = null
)
}
}
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
_uiState.update { currentState ->
currentState.copy(
cipherEncryptDatabase = cipherEncryptDatabase
)
}
}
fun consumeCredentialEncrypted() {
_uiState.update { currentState ->
currentState.copy(
cipherEncryptDatabase = null
)
}
}
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
_uiState.update { currentState ->
currentState.copy(
cipherDecryptDatabase = cipherDecryptDatabase
)
}
}
fun consumeCredentialDecrypted() {
_uiState.update { currentState ->
currentState.copy(
cipherDecryptDatabase = null
)
}
}
fun deleteData() {
_uiState.update { currentState ->
currentState.copy(
initAdvancedUnlockMode = false,
databaseFileUri = null,
isCredentialRequired = false,
credential = null,
isConditionToStoreCredentialVerified = false,
onUnlockAvailabilityCheckRequested = false,
cipherEncryptDatabase = null,
cipherDecryptDatabase = null
)
}
}
}
data class DeviceUnlockUiStates(
val initAdvancedUnlockMode: Boolean = false,
val databaseFileUri: Uri? = null,
val isCredentialRequired: Boolean = false,
val credential: ByteArray? = null,
val isConditionToStoreCredentialVerified: Boolean = false,
val onUnlockAvailabilityCheckRequested: Boolean = false,
val cipherEncryptDatabase: CipherEncryptDatabase? = null,
val cipherDecryptDatabase: CipherDecryptDatabase? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DeviceUnlockUiStates
if (isCredentialRequired != other.isCredentialRequired) return false
if (isConditionToStoreCredentialVerified != other.isConditionToStoreCredentialVerified) return false
if (onUnlockAvailabilityCheckRequested != other.onUnlockAvailabilityCheckRequested) return false
if (!credential.contentEquals(other.credential)) return false
if (cipherEncryptDatabase != other.cipherEncryptDatabase) return false
if (cipherDecryptDatabase != other.cipherDecryptDatabase) return false
return true
}
override fun hashCode(): Int {
var result = isCredentialRequired.hashCode()
result = 31 * result + isConditionToStoreCredentialVerified.hashCode()
result = 31 * result + onUnlockAvailabilityCheckRequested.hashCode()
result = 31 * result + (credential?.contentHashCode() ?: 0)
result = 31 * result + (cipherEncryptDatabase?.hashCode() ?: 0)
result = 31 * result + (cipherDecryptDatabase?.hashCode() ?: 0)
return result
}
} }