diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt index 6a55ac16f..17bcea82d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt @@ -434,8 +434,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(), when (item.itemId) { android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url) } - - return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item) + MenuUtil.onDefaultMenuOptionsItemSelected(this, item) + return super.onOptionsItemSelected(item) } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index 6f1db15b3..15753f38a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -37,8 +37,8 @@ import android.widget.* import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.widget.Toolbar -import androidx.biometric.BiometricManager import androidx.core.app.ActivityCompat +import androidx.fragment.app.commit import com.google.android.material.snackbar.Snackbar import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog @@ -50,8 +50,8 @@ import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.autofill.AutofillHelper +import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment import com.kunzisoft.keepass.biometric.AdvancedUnlockManager -import com.kunzisoft.keepass.biometric.AdvancedUnlockHelper import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException @@ -69,14 +69,13 @@ import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.UriUtil -import com.kunzisoft.keepass.view.AdvancedUnlockInfoView import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel import kotlinx.android.synthetic.main.activity_password.* import java.io.FileNotFoundException -open class PasswordActivity : SpecialModeActivity() { +open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockManager.BuilderListener { // Views private var toolbar: Toolbar? = null @@ -86,7 +85,7 @@ open class PasswordActivity : SpecialModeActivity() { private var confirmButtonView: Button? = null private var checkboxPasswordView: CompoundButton? = null private var checkboxKeyFileView: CompoundButton? = null - private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null + private var advancedUnlockFragment: AdvancedUnlockFragment? = null private var infoContainerView: ViewGroup? = null private val databaseFileViewModel: DatabaseFileViewModel by viewModels() @@ -113,7 +112,6 @@ open class PasswordActivity : SpecialModeActivity() { private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null - private var advancedUnlockManager: AdvancedUnlockManager? = null private var mAllowAutoOpenBiometricPrompt: Boolean = true override fun onCreate(savedInstanceState: Bundle?) { @@ -133,7 +131,6 @@ open class PasswordActivity : SpecialModeActivity() { keyFileSelectionView = findViewById(R.id.keyfile_selection) checkboxPasswordView = findViewById(R.id.password_checkbox) checkboxKeyFileView = findViewById(R.id.keyfile_checkox) - advancedUnlockInfoView = findViewById(R.id.biometric_info) infoContainerView = findViewById(R.id.activity_password_info_container) mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked @@ -170,47 +167,20 @@ open class PasswordActivity : SpecialModeActivity() { } // Init Biometric elements - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (PreferencesUtil.isAdvancedUnlockEnable(this)) { - advancedUnlockManager = AdvancedUnlockManager(this, advancedUnlockInfoView!!, - object: AdvancedUnlockManager.BuilderListener { - - override fun retrieveCredentialForEncryption(): String { - return passwordView?.text?.toString() ?: "" - } - - override fun conditionToStoreCredential(): Boolean { - return checkboxPasswordView?.isChecked == true - } - - override fun onCredentialEncrypted(databaseUri: Uri, - encryptedCredential: String, - ivSpec: String) { - // Load the database if password is registered with biometric - verifyCheckboxesAndLoadDatabase( - CipherDatabaseEntity( - databaseUri.toString(), - encryptedCredential, - ivSpec) - ) - } - - override fun onCredentialDecrypted(databaseUri: Uri, - decryptedCredential: String) { - // Load the database if password is retrieve from biometric - // Retrieve from biometric - verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential) - } - } - ) + advancedUnlockFragment = supportFragmentManager + .findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment? + if (advancedUnlockFragment == null) { + advancedUnlockFragment = AdvancedUnlockFragment() + supportFragmentManager.commit { + replace(R.id.fragment_advanced_unlock_container_view, + advancedUnlockFragment!!, + UNLOCK_FRAGMENT_TAG) } } // Listen password checkbox to init advanced unlock and confirmation button checkboxPasswordView?.setOnCheckedChangeListener { _, _ -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - advancedUnlockManager?.checkUnlockAvailability() - } + advancedUnlockFragment?.checkUnlockAvailability() enableOrNotTheConfirmationButton() } @@ -247,12 +217,7 @@ open class PasswordActivity : SpecialModeActivity() { when (actionTask) { ACTION_DATABASE_LOAD_TASK -> { // Recheck advanced unlock if error - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (PreferencesUtil.isAdvancedUnlockEnable(this@PasswordActivity)) { - // Stay with the same mode and init it - advancedUnlockManager?.initAdvancedUnlockMode() - } - } + advancedUnlockFragment?.initAdvancedUnlockMode() if (result.isSuccess) { mDatabaseKeyFileUri = null @@ -360,6 +325,33 @@ open class PasswordActivity : SpecialModeActivity() { finish() } + override fun retrieveCredentialForEncryption(): String { + return passwordView?.text?.toString() ?: "" + } + + override fun conditionToStoreCredential(): Boolean { + return checkboxPasswordView?.isChecked == true + } + + override fun onCredentialEncrypted(databaseUri: Uri, + encryptedCredential: String, + ivSpec: String) { + // Load the database if password is registered with biometric + verifyCheckboxesAndLoadDatabase( + CipherDatabaseEntity( + databaseUri.toString(), + encryptedCredential, + ivSpec) + ) + } + + override fun onCredentialDecrypted(databaseUri: Uri, + decryptedCredential: String) { + // Load the database if password is retrieve from biometric + // Retrieve from biometric + verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential) + } + private val onEditorActionListener = object : TextView.OnEditorActionListener { override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { if (actionId == IME_ACTION_DONE) { @@ -426,18 +418,9 @@ open class PasswordActivity : SpecialModeActivity() { verifyCheckboxesAndLoadDatabase(password, keyFileUri) } else { // Init Biometric elements - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - databaseFileUri?.let { databaseUri -> - if (PreferencesUtil.isAdvancedUnlockEnable(this)) { - advancedUnlockManager?.connect(databaseUri) - advancedUnlockManager?.isBiometricPromptAutoOpenEnable = - mAllowAutoOpenBiometricPrompt - && mProgressDatabaseTaskProvider?.isBinded() != true - } else { - advancedUnlockManager?.disconnect() - } - } - } + advancedUnlockFragment?.loadDatabase(databaseFileUri, + mAllowAutoOpenBiometricPrompt + && mProgressDatabaseTaskProvider?.isBinded() != true) } enableOrNotTheConfirmationButton() @@ -489,10 +472,6 @@ open class PasswordActivity : SpecialModeActivity() { override fun onPause() { mProgressDatabaseTaskProvider?.unregisterProgressTask() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - advancedUnlockManager?.disconnect() - } - // Reinit locking activity UI variable LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null mAllowAutoOpenBiometricPrompt = true @@ -500,12 +479,6 @@ open class PasswordActivity : SpecialModeActivity() { super.onPause() } - override fun onDestroy() { - advancedUnlockManager = null - - super.onDestroy() - } - override fun onSaveInstanceState(outState: Bundle) { outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked) mDatabaseKeyFileUri?.let { @@ -607,11 +580,6 @@ open class PasswordActivity : SpecialModeActivity() { MenuUtil.defaultMenuInflater(inflater, menu) } - if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // biometric menu - advancedUnlockManager?.inflateOptionsMenu(inflater, menu) - } - super.onCreateOptionsMenu(menu) launchEducation(menu) @@ -687,14 +655,16 @@ open class PasswordActivity : SpecialModeActivity() { performedNextEducation(passwordActivityEducation, menu) }) + // TODO Education + /* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !readOnlyEducationPerformed) { val biometricCanAuthenticate = AdvancedUnlockHelper.canAuthenticate(this) PreferencesUtil.isAdvancedUnlockEnable(applicationContext) && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) - && advancedUnlockInfoView != null && advancedUnlockInfoView?.visibility == View.VISIBLE - && advancedUnlockInfoView?.unlockIconImageView != null - && passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!, + && advancedUnlockFragment != null && advancedUnlockFragment?.visibility == View.VISIBLE + && advancedUnlockFragment?.unlockIconImageView != null + && passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockFragment?.unlockIconImageView!!, { performedNextEducation(passwordActivityEducation, menu) }, @@ -702,6 +672,8 @@ open class PasswordActivity : SpecialModeActivity() { performedNextEducation(passwordActivityEducation, menu) }) } + + */ } } @@ -723,10 +695,7 @@ open class PasswordActivity : SpecialModeActivity() { readOnly = !readOnly changeOpenFileReadIcon(item) } - R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - advancedUnlockManager?.deleteEncryptedDatabaseKey() - } - else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) + else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item) } return super.onOptionsItemSelected(item) @@ -741,9 +710,7 @@ open class PasswordActivity : SpecialModeActivity() { mAllowAutoOpenBiometricPrompt = false // To get device credential unlock result - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - advancedUnlockManager?.onActivityResult(requestCode, resultCode, data) - } + advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data) // To get entry in result if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -778,6 +745,8 @@ open class PasswordActivity : SpecialModeActivity() { private val TAG = PasswordActivity::class.java.name + private const val UNLOCK_FRAGMENT_TAG = "UNLOCK_FRAGMENT_TAG" + private const val KEY_FILENAME = "fileName" private const val KEY_KEYFILE = "keyFile" private const val VIEW_INTENT = "android.intent.action.VIEW" diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt new file mode 100644 index 000000000..5f945c7ae --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt @@ -0,0 +1,149 @@ +package com.kunzisoft.keepass.biometric + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.view.* +import androidx.fragment.app.FragmentActivity +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.activities.stylish.StylishFragment +import com.kunzisoft.keepass.settings.PreferencesUtil + +class AdvancedUnlockFragment: StylishFragment() { + + private var advancedUnlockManager: AdvancedUnlockManager? = null + + private var advancedUnlockEnabled = false + + override fun onAttach(context: Context) { + super.onAttach(context) + + advancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (advancedUnlockEnabled) { + advancedUnlockManager?.builderListener = context as AdvancedUnlockManager.BuilderListener + } + } + } catch (e: ClassCastException) { + throw ClassCastException(context.toString() + + " must implement " + AdvancedUnlockManager.BuilderListener::class.java.name) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (advancedUnlockEnabled) { + if (advancedUnlockManager == null) { + advancedUnlockManager = AdvancedUnlockManager { context as FragmentActivity } + } else { + advancedUnlockManager?.retrieveContext = { context as FragmentActivity } + } + } + } + + retainInstance = true + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + super.onCreateView(inflater, container, savedInstanceState) + + val rootView = inflater.cloneInContext(contextThemed) + .inflate(R.layout.fragment_advanced_unlock, container, false) + + advancedUnlockManager?.advancedUnlockInfoView = rootView.findViewById(R.id.advanced_unlock_view) + + return rootView + } + + private data class ActivityResult(var requestCode: Int, var resultCode: Int, var data: Intent?) + private var activityResult: ActivityResult? = null + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + // To wait resume + activityResult = ActivityResult(requestCode, resultCode, data) + + super.onActivityResult(requestCode, resultCode, data) + } + + override fun onResume() { + super.onResume() + advancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext()) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + + if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // biometric menu + advancedUnlockManager?.inflateOptionsMenu(inflater, menu) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + advancedUnlockManager?.deleteEncryptedDatabaseKey() + } + } + + return super.onOptionsItemSelected(item) + } + + fun loadDatabase(databaseUri: Uri?, autoOpenPrompt: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // To get device credential unlock result, only if same database uri + activityResult?.let { + if (databaseUri != null && advancedUnlockManager?.databaseFileUri == databaseUri) { + advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode, it.data) + } + } ?: run { + if (databaseUri != null && advancedUnlockEnabled) { + advancedUnlockManager?.connect(databaseUri) + advancedUnlockManager?.autoOpenPrompt = autoOpenPrompt + } else { + advancedUnlockManager?.disconnect() + } + } + activityResult = null + } + } + + /** + * Check unlock availability and change the current mode depending of device's state + */ + fun checkUnlockAvailability() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + advancedUnlockManager?.checkUnlockAvailability() + } + } + + fun initAdvancedUnlockMode() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (advancedUnlockEnabled) { + advancedUnlockManager?.initAdvancedUnlockMode() + } + } + } + + override fun onDestroy() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + advancedUnlockManager?.disconnect() + advancedUnlockManager?.builderListener = null + advancedUnlockManager = null + } + + super.onDestroy() + } + + override fun onDetach() { + advancedUnlockManager?.builderListener = null + + super.onDetach() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockHelper.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockHelper.kt index 45165b62c..f8638bfe1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockHelper.kt @@ -47,7 +47,7 @@ import javax.crypto.SecretKey import javax.crypto.spec.IvParameterSpec @RequiresApi(api = Build.VERSION_CODES.M) -class AdvancedUnlockHelper(private val context: FragmentActivity) { +class AdvancedUnlockHelper(private var retrieveContext: () -> FragmentActivity) { private var keyStore: KeyStore? = null private var keyGenerator: KeyGenerator? = null @@ -72,8 +72,8 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) { private var isKeyManagerInit = false - private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(context) - private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(context) + private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext()) + private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(retrieveContext()) val isKeyManagerInitialized: Boolean get() { @@ -99,7 +99,7 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) { } init { - if (isDeviceSecure(context) + if (isDeviceSecure(retrieveContext()) && (deviceCredentialUnlockEnable || biometricUnlockEnable)) { try { this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE) @@ -144,11 +144,6 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) { // Require the user to authenticate with a fingerprint to authorize every use // of the key .setUserAuthenticationRequired(true) - .apply { - if (isDeviceCredentialBiometricOperation()) { - setUserAuthenticationParameters(0, KeyProperties.AUTH_DEVICE_CREDENTIAL) - } - } .build()) keyGenerator?.generateKey() } @@ -287,20 +282,20 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) { fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) { // Init advanced unlock prompt if (biometricPrompt == null) { - biometricPrompt = BiometricPrompt(context, + biometricPrompt = BiometricPrompt(retrieveContext(), Executors.newSingleThreadExecutor(), authenticationCallback) } - val promptTitle = context.getString(cryptoPrompt.promptTitleId) + val promptTitle = retrieveContext().getString(cryptoPrompt.promptTitleId) val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId -> - context.getString(descriptionId) + retrieveContext().getString(descriptionId) } ?: "" if (cryptoPrompt.isDeviceCredentialOperation) { // TODO open intent keyguard for response - val keyGuardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java) - context.startActivityForResult( + val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java) + retrieveContext().startActivityForResult( keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription), REQUEST_DEVICE_CREDENTIAL) } @@ -313,7 +308,7 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) { if (isDeviceCredentialBiometricOperation()) { setAllowedAuthenticators(DEVICE_CREDENTIAL) } else { - setNegativeButtonText(context.getString(android.R.string.cancel)) + setNegativeButtonText(retrieveContext().getString(android.R.string.cancel)) } }.build() @@ -449,9 +444,9 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) { * Remove entry key in keystore */ @RequiresApi(api = Build.VERSION_CODES.M) - fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity, + fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity, advancedCallback: AdvancedUnlockErrorCallback) { - AdvancedUnlockHelper(context).apply { + AdvancedUnlockHelper{ fragmentActivity }.apply { advancedUnlockCallback = object : AdvancedUnlockCallback { override fun onAuthenticationSucceeded() {} diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt index 714d863b3..55d818016 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt @@ -39,15 +39,16 @@ import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.view.AdvancedUnlockInfoView @RequiresApi(api = Build.VERSION_CODES.M) -class AdvancedUnlockManager(var context: FragmentActivity, - private var advancedUnlockInfoView: AdvancedUnlockInfoView, - private var builderListener: BuilderListener) +class AdvancedUnlockManager(var retrieveContext: () -> FragmentActivity) : AdvancedUnlockHelper.AdvancedUnlockCallback { private var advancedUnlockHelper: AdvancedUnlockHelper? = null private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE + lateinit var advancedUnlockInfoView: AdvancedUnlockInfoView + var builderListener: BuilderListener ? = null - private var databaseFileUri: Uri? = null + var databaseFileUri: Uri? = null + private set // Only to fix multiple fingerprint menu #332 private var mAllowAdvancedUnlockMenu = false @@ -56,8 +57,8 @@ class AdvancedUnlockManager(var context: FragmentActivity, /** * Manage setting to auto open biometric prompt */ - private var biometricPromptAutoOpenPreference = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context) - var isBiometricPromptAutoOpenEnable: Boolean = false + private var biometricPromptAutoOpenPreference = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(retrieveContext()) + var autoOpenPrompt: Boolean = false get() { return field && biometricPromptAutoOpenPreference } @@ -66,7 +67,7 @@ class AdvancedUnlockManager(var context: FragmentActivity, // checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization private var allowOpenBiometricPrompt = false - private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext) + private var cipherDatabaseAction = CipherDatabaseAction.getInstance(retrieveContext().applicationContext) private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null @@ -76,18 +77,18 @@ class AdvancedUnlockManager(var context: FragmentActivity, */ fun checkUnlockAvailability() { - if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) { + if (PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext())) { advancedUnlockInfoView.setIconResource(R.drawable.bolt) - } else if (PreferencesUtil.isBiometricUnlockEnable(context)) { + } else if (PreferencesUtil.isBiometricUnlockEnable(retrieveContext())) { advancedUnlockInfoView.setIconResource(R.drawable.fingerprint) } // biometric not supported (by API level or hardware) so keep option hidden // or manually disable - val biometricCanAuthenticate = AdvancedUnlockHelper.canAuthenticate(context) + val biometricCanAuthenticate = AdvancedUnlockHelper.canAuthenticate(retrieveContext()) allowOpenBiometricPrompt = true - if (!PreferencesUtil.isAdvancedUnlockEnable(context) + if (!PreferencesUtil.isAdvancedUnlockEnable(retrieveContext()) || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) { toggleMode(Mode.BIOMETRIC_UNAVAILABLE) @@ -101,7 +102,7 @@ class AdvancedUnlockManager(var context: FragmentActivity, // Check if fingerprint well init (be called the first time the fingerprint is configured // and the activity still active) if (advancedUnlockHelper?.isKeyManagerInitialized != true) { - advancedUnlockHelper = AdvancedUnlockHelper(context) + advancedUnlockHelper = AdvancedUnlockHelper(retrieveContext) // callback for fingerprint findings advancedUnlockHelper?.advancedUnlockCallback = this } @@ -109,7 +110,7 @@ class AdvancedUnlockManager(var context: FragmentActivity, if (advancedUnlockHelper?.isKeyManagerInitialized != true) { toggleMode(Mode.KEY_MANAGER_UNAVAILABLE) } else { - if (builderListener.conditionToStoreCredential()) { + if (builderListener?.conditionToStoreCredential() == true) { // listen for encryption toggleMode(Mode.STORE_CREDENTIAL) } else { @@ -148,7 +149,7 @@ class AdvancedUnlockManager(var context: FragmentActivity, private fun openBiometricSetting() { advancedUnlockInfoView.setIconViewClickListener(false) { // ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices... - context.startActivity(Intent(Settings.ACTION_SETTINGS)) + retrieveContext().startActivity(Intent(Settings.ACTION_SETTINGS)) } } @@ -181,12 +182,12 @@ class AdvancedUnlockManager(var context: FragmentActivity, advancedUnlockInfoView.setIconViewClickListener(false) { onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS, - context.getString(R.string.credential_before_click_advanced_unlock_button)) + retrieveContext().getString(R.string.credential_before_click_advanced_unlock_button)) } } private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) { - context.runOnUiThread { + retrieveContext().runOnUiThread { if (allowOpenBiometricPrompt) { try { advancedUnlockHelper @@ -229,8 +230,8 @@ class AdvancedUnlockManager(var context: FragmentActivity, } // Auto open the biometric prompt - if (isBiometricPromptAutoOpenEnable) { - isBiometricPromptAutoOpenEnable = false + if (autoOpenPrompt) { + autoOpenPrompt = false openAdvancedUnlockPrompt(cryptoPrompt) } } @@ -266,7 +267,7 @@ class AdvancedUnlockManager(var context: FragmentActivity, && (biometricMode != Mode.BIOMETRIC_UNAVAILABLE && biometricMode != Mode.KEY_MANAGER_UNAVAILABLE) mAddBiometricMenuInProgress = false - context.invalidateOptionsMenu() + retrieveContext().invalidateOptionsMenu() } } } @@ -322,21 +323,21 @@ class AdvancedUnlockManager(var context: FragmentActivity, } override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - context.runOnUiThread { + retrieveContext().runOnUiThread { Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString") setAdvancedUnlockedMessageView(errString.toString()) } } override fun onAuthenticationFailed() { - context.runOnUiThread { + retrieveContext().runOnUiThread { Log.e(TAG, "Biometric authentication failed, biometric not recognized") setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized) } } override fun onAuthenticationSucceeded() { - context.runOnUiThread { + retrieveContext().runOnUiThread { when (biometricMode) { Mode.BIOMETRIC_UNAVAILABLE -> { } @@ -350,8 +351,10 @@ class AdvancedUnlockManager(var context: FragmentActivity, } Mode.STORE_CREDENTIAL -> { // newly store the entered password in encrypted way - advancedUnlockHelper?.encryptData(builderListener.retrieveCredentialForEncryption()) - AdvancedUnlockNotificationService.startServiceForTimeout(context) + builderListener?.retrieveCredentialForEncryption()?.let { credential -> + advancedUnlockHelper?.encryptData(credential) + } + AdvancedUnlockNotificationService.startServiceForTimeout(retrieveContext()) } Mode.EXTRACT_CREDENTIAL -> { // retrieve the encrypted value from preferences @@ -369,14 +372,14 @@ class AdvancedUnlockManager(var context: FragmentActivity, override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) { databaseFileUri?.let { databaseUri -> - builderListener.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec) + builderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec) } } override fun handleDecryptedResult(decryptedValue: String) { // Load database directly with password retrieve databaseFileUri?.let { - builderListener.onCredentialDecrypted(it, decryptedValue) + builderListener?.onCredentialDecrypted(it, decryptedValue) } } @@ -390,25 +393,25 @@ class AdvancedUnlockManager(var context: FragmentActivity, } private fun showFingerPrintViews(show: Boolean) { - context.runOnUiThread { + retrieveContext().runOnUiThread { advancedUnlockInfoView.visibility = if (show) View.VISIBLE else View.GONE } } private fun setAdvancedUnlockedTitleView(textId: Int) { - context.runOnUiThread { + retrieveContext().runOnUiThread { advancedUnlockInfoView.setTitle(textId) } } private fun setAdvancedUnlockedMessageView(textId: Int) { - context.runOnUiThread { + retrieveContext().runOnUiThread { advancedUnlockInfoView.setMessage(textId) } } private fun setAdvancedUnlockedMessageView(text: CharSequence) { - context.runOnUiThread { + retrieveContext().runOnUiThread { advancedUnlockInfoView.message = text } } diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt index 63d5cb7dc..38a311fc2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt @@ -53,23 +53,19 @@ object MenuUtil { fun onDefaultMenuOptionsItemSelected(activity: Activity, item: MenuItem, readOnly: Boolean = READ_ONLY_DEFAULT, - timeoutEnable: Boolean = false): Boolean { + timeoutEnable: Boolean = false) { when (item.itemId) { R.id.menu_contribute -> { onContributionItemSelected(activity) - return true } R.id.menu_app_settings -> { // To avoid flickering when launch settings in a LockingActivity SettingsActivity.launch(activity, readOnly, timeoutEnable) - return true } R.id.menu_about -> { val intent = Intent(activity, AboutActivity::class.java) activity.startActivity(intent) - return true } - else -> return true } } } diff --git a/app/src/main/res/layout/activity_password.xml b/app/src/main/res/layout/activity_password.xml index 52e1f3ed2..1955fea2d 100644 --- a/app/src/main/res/layout/activity_password.xml +++ b/app/src/main/res/layout/activity_password.xml @@ -68,17 +68,10 @@ android:padding="0dp" android:contentDescription="@string/about" android:src="@drawable/ic_launcher_foreground"/> - - - + android:layout_height="match_parent" /> + + + \ No newline at end of file