Add fragment

This commit is contained in:
J-Jamet
2020-12-13 14:18:21 +01:00
parent bd0b5b0954
commit 6be0457947
8 changed files with 268 additions and 150 deletions

View File

@@ -434,8 +434,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
when (item.itemId) { when (item.itemId) {
android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url) android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
} }
MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
companion object { companion object {

View File

@@ -37,8 +37,8 @@ import android.widget.*
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.fragment.app.commit
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
@@ -50,8 +50,8 @@ import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.biometric.AdvancedUnlockHelper
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException 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.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import kotlinx.android.synthetic.main.activity_password.* import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException import java.io.FileNotFoundException
open class PasswordActivity : SpecialModeActivity() { open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockManager.BuilderListener {
// Views // Views
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
@@ -86,7 +85,7 @@ open class PasswordActivity : SpecialModeActivity() {
private var confirmButtonView: Button? = null private var confirmButtonView: Button? = null
private var checkboxPasswordView: CompoundButton? = null private var checkboxPasswordView: CompoundButton? = null
private var checkboxKeyFileView: CompoundButton? = null private var checkboxKeyFileView: CompoundButton? = null
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null private var advancedUnlockFragment: AdvancedUnlockFragment? = null
private var infoContainerView: ViewGroup? = null private var infoContainerView: ViewGroup? = null
private val databaseFileViewModel: DatabaseFileViewModel by viewModels() private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
@@ -113,7 +112,6 @@ open class PasswordActivity : SpecialModeActivity() {
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var advancedUnlockManager: AdvancedUnlockManager? = null
private var mAllowAutoOpenBiometricPrompt: Boolean = true private var mAllowAutoOpenBiometricPrompt: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -133,7 +131,6 @@ open class PasswordActivity : SpecialModeActivity() {
keyFileSelectionView = findViewById(R.id.keyfile_selection) keyFileSelectionView = findViewById(R.id.keyfile_selection)
checkboxPasswordView = findViewById(R.id.password_checkbox) checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox) checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
advancedUnlockInfoView = findViewById(R.id.biometric_info)
infoContainerView = findViewById(R.id.activity_password_info_container) infoContainerView = findViewById(R.id.activity_password_info_container)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
@@ -170,47 +167,20 @@ open class PasswordActivity : SpecialModeActivity() {
} }
// Init Biometric elements // Init Biometric elements
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { advancedUnlockFragment = supportFragmentManager
if (PreferencesUtil.isAdvancedUnlockEnable(this)) { .findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
advancedUnlockManager = AdvancedUnlockManager(this, advancedUnlockInfoView!!, if (advancedUnlockFragment == null) {
object: AdvancedUnlockManager.BuilderListener { advancedUnlockFragment = AdvancedUnlockFragment()
supportFragmentManager.commit {
override fun retrieveCredentialForEncryption(): String { replace(R.id.fragment_advanced_unlock_container_view,
return passwordView?.text?.toString() ?: "" advancedUnlockFragment!!,
} UNLOCK_FRAGMENT_TAG)
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)
}
}
)
} }
} }
// Listen password checkbox to init advanced unlock and confirmation button // Listen password checkbox to init advanced unlock and confirmation button
checkboxPasswordView?.setOnCheckedChangeListener { _, _ -> checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { advancedUnlockFragment?.checkUnlockAvailability()
advancedUnlockManager?.checkUnlockAvailability()
}
enableOrNotTheConfirmationButton() enableOrNotTheConfirmationButton()
} }
@@ -247,12 +217,7 @@ open class PasswordActivity : SpecialModeActivity() {
when (actionTask) { when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> { ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error // Recheck advanced unlock if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { advancedUnlockFragment?.initAdvancedUnlockMode()
if (PreferencesUtil.isAdvancedUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockManager?.initAdvancedUnlockMode()
}
}
if (result.isSuccess) { if (result.isSuccess) {
mDatabaseKeyFileUri = null mDatabaseKeyFileUri = null
@@ -360,6 +325,33 @@ open class PasswordActivity : SpecialModeActivity() {
finish() 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 { private val onEditorActionListener = object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == IME_ACTION_DONE) { if (actionId == IME_ACTION_DONE) {
@@ -426,18 +418,9 @@ open class PasswordActivity : SpecialModeActivity() {
verifyCheckboxesAndLoadDatabase(password, keyFileUri) verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else { } else {
// Init Biometric elements // Init Biometric elements
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { advancedUnlockFragment?.loadDatabase(databaseFileUri,
databaseFileUri?.let { databaseUri ->
if (PreferencesUtil.isAdvancedUnlockEnable(this)) {
advancedUnlockManager?.connect(databaseUri)
advancedUnlockManager?.isBiometricPromptAutoOpenEnable =
mAllowAutoOpenBiometricPrompt mAllowAutoOpenBiometricPrompt
&& mProgressDatabaseTaskProvider?.isBinded() != true && mProgressDatabaseTaskProvider?.isBinded() != true)
} else {
advancedUnlockManager?.disconnect()
}
}
}
} }
enableOrNotTheConfirmationButton() enableOrNotTheConfirmationButton()
@@ -489,10 +472,6 @@ open class PasswordActivity : SpecialModeActivity() {
override fun onPause() { override fun onPause() {
mProgressDatabaseTaskProvider?.unregisterProgressTask() mProgressDatabaseTaskProvider?.unregisterProgressTask()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockManager?.disconnect()
}
// Reinit locking activity UI variable // Reinit locking activity UI variable
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true mAllowAutoOpenBiometricPrompt = true
@@ -500,12 +479,6 @@ open class PasswordActivity : SpecialModeActivity() {
super.onPause() super.onPause()
} }
override fun onDestroy() {
advancedUnlockManager = null
super.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked) outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
mDatabaseKeyFileUri?.let { mDatabaseKeyFileUri?.let {
@@ -607,11 +580,6 @@ open class PasswordActivity : SpecialModeActivity() {
MenuUtil.defaultMenuInflater(inflater, menu) MenuUtil.defaultMenuInflater(inflater, menu)
} }
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
advancedUnlockManager?.inflateOptionsMenu(inflater, menu)
}
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
launchEducation(menu) launchEducation(menu)
@@ -687,14 +655,16 @@ open class PasswordActivity : SpecialModeActivity() {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu)
}) })
// TODO Education
/*
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) { && !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockHelper.canAuthenticate(this) val biometricCanAuthenticate = AdvancedUnlockHelper.canAuthenticate(this)
PreferencesUtil.isAdvancedUnlockEnable(applicationContext) PreferencesUtil.isAdvancedUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.visibility == View.VISIBLE && advancedUnlockFragment != null && advancedUnlockFragment?.visibility == View.VISIBLE
&& advancedUnlockInfoView?.unlockIconImageView != null && advancedUnlockFragment?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!, && passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockFragment?.unlockIconImageView!!,
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu)
}, },
@@ -702,6 +672,8 @@ open class PasswordActivity : SpecialModeActivity() {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu)
}) })
} }
*/
} }
} }
@@ -723,10 +695,7 @@ open class PasswordActivity : SpecialModeActivity() {
readOnly = !readOnly readOnly = !readOnly
changeOpenFileReadIcon(item) changeOpenFileReadIcon(item)
} }
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
advancedUnlockManager?.deleteEncryptedDatabaseKey()
}
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
@@ -741,9 +710,7 @@ open class PasswordActivity : SpecialModeActivity() {
mAllowAutoOpenBiometricPrompt = false mAllowAutoOpenBiometricPrompt = false
// To get device credential unlock result // To get device credential unlock result
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data)
advancedUnlockManager?.onActivityResult(requestCode, resultCode, data)
}
// To get entry in result // To get entry in result
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 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 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_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile" private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW" private const val VIEW_INTENT = "android.intent.action.VIEW"

View File

@@ -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()
}
}

View File

@@ -47,7 +47,7 @@ import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
@RequiresApi(api = Build.VERSION_CODES.M) @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 keyStore: KeyStore? = null
private var keyGenerator: KeyGenerator? = null private var keyGenerator: KeyGenerator? = null
@@ -72,8 +72,8 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) {
private var isKeyManagerInit = false private var isKeyManagerInit = false
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(context) private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext())
private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(context) private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(retrieveContext())
val isKeyManagerInitialized: Boolean val isKeyManagerInitialized: Boolean
get() { get() {
@@ -99,7 +99,7 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) {
} }
init { init {
if (isDeviceSecure(context) if (isDeviceSecure(retrieveContext())
&& (deviceCredentialUnlockEnable || biometricUnlockEnable)) { && (deviceCredentialUnlockEnable || biometricUnlockEnable)) {
try { try {
this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE) 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 // Require the user to authenticate with a fingerprint to authorize every use
// of the key // of the key
.setUserAuthenticationRequired(true) .setUserAuthenticationRequired(true)
.apply {
if (isDeviceCredentialBiometricOperation()) {
setUserAuthenticationParameters(0, KeyProperties.AUTH_DEVICE_CREDENTIAL)
}
}
.build()) .build())
keyGenerator?.generateKey() keyGenerator?.generateKey()
} }
@@ -287,20 +282,20 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) {
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) { fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
// Init advanced unlock prompt // Init advanced unlock prompt
if (biometricPrompt == null) { if (biometricPrompt == null) {
biometricPrompt = BiometricPrompt(context, biometricPrompt = BiometricPrompt(retrieveContext(),
Executors.newSingleThreadExecutor(), Executors.newSingleThreadExecutor(),
authenticationCallback) authenticationCallback)
} }
val promptTitle = context.getString(cryptoPrompt.promptTitleId) val promptTitle = retrieveContext().getString(cryptoPrompt.promptTitleId)
val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId -> val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId ->
context.getString(descriptionId) retrieveContext().getString(descriptionId)
} ?: "" } ?: ""
if (cryptoPrompt.isDeviceCredentialOperation) { if (cryptoPrompt.isDeviceCredentialOperation) {
// TODO open intent keyguard for response // TODO open intent keyguard for response
val keyGuardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java) val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java)
context.startActivityForResult( retrieveContext().startActivityForResult(
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription), keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription),
REQUEST_DEVICE_CREDENTIAL) REQUEST_DEVICE_CREDENTIAL)
} }
@@ -313,7 +308,7 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) {
if (isDeviceCredentialBiometricOperation()) { if (isDeviceCredentialBiometricOperation()) {
setAllowedAuthenticators(DEVICE_CREDENTIAL) setAllowedAuthenticators(DEVICE_CREDENTIAL)
} else { } else {
setNegativeButtonText(context.getString(android.R.string.cancel)) setNegativeButtonText(retrieveContext().getString(android.R.string.cancel))
} }
}.build() }.build()
@@ -449,9 +444,9 @@ class AdvancedUnlockHelper(private val context: FragmentActivity) {
* Remove entry key in keystore * Remove entry key in keystore
*/ */
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity, fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity,
advancedCallback: AdvancedUnlockErrorCallback) { advancedCallback: AdvancedUnlockErrorCallback) {
AdvancedUnlockHelper(context).apply { AdvancedUnlockHelper{ fragmentActivity }.apply {
advancedUnlockCallback = object : AdvancedUnlockCallback { advancedUnlockCallback = object : AdvancedUnlockCallback {
override fun onAuthenticationSucceeded() {} override fun onAuthenticationSucceeded() {}

View File

@@ -39,15 +39,16 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
class AdvancedUnlockManager(var context: FragmentActivity, class AdvancedUnlockManager(var retrieveContext: () -> FragmentActivity)
private var advancedUnlockInfoView: AdvancedUnlockInfoView,
private var builderListener: BuilderListener)
: AdvancedUnlockHelper.AdvancedUnlockCallback { : AdvancedUnlockHelper.AdvancedUnlockCallback {
private var advancedUnlockHelper: AdvancedUnlockHelper? = null private var advancedUnlockHelper: AdvancedUnlockHelper? = null
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE 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 // Only to fix multiple fingerprint menu #332
private var mAllowAdvancedUnlockMenu = false private var mAllowAdvancedUnlockMenu = false
@@ -56,8 +57,8 @@ class AdvancedUnlockManager(var context: FragmentActivity,
/** /**
* Manage setting to auto open biometric prompt * Manage setting to auto open biometric prompt
*/ */
private var biometricPromptAutoOpenPreference = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context) private var biometricPromptAutoOpenPreference = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(retrieveContext())
var isBiometricPromptAutoOpenEnable: Boolean = false var autoOpenPrompt: Boolean = false
get() { get() {
return field && biometricPromptAutoOpenPreference return field && biometricPromptAutoOpenPreference
} }
@@ -66,7 +67,7 @@ class AdvancedUnlockManager(var context: FragmentActivity,
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization // checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
private var allowOpenBiometricPrompt = false private var allowOpenBiometricPrompt = false
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext) private var cipherDatabaseAction = CipherDatabaseAction.getInstance(retrieveContext().applicationContext)
private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null
@@ -76,18 +77,18 @@ class AdvancedUnlockManager(var context: FragmentActivity,
*/ */
fun checkUnlockAvailability() { fun checkUnlockAvailability() {
if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) { if (PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext())) {
advancedUnlockInfoView.setIconResource(R.drawable.bolt) advancedUnlockInfoView.setIconResource(R.drawable.bolt)
} else if (PreferencesUtil.isBiometricUnlockEnable(context)) { } else if (PreferencesUtil.isBiometricUnlockEnable(retrieveContext())) {
advancedUnlockInfoView.setIconResource(R.drawable.fingerprint) advancedUnlockInfoView.setIconResource(R.drawable.fingerprint)
} }
// biometric not supported (by API level or hardware) so keep option hidden // biometric not supported (by API level or hardware) so keep option hidden
// or manually disable // or manually disable
val biometricCanAuthenticate = AdvancedUnlockHelper.canAuthenticate(context) val biometricCanAuthenticate = AdvancedUnlockHelper.canAuthenticate(retrieveContext())
allowOpenBiometricPrompt = true allowOpenBiometricPrompt = true
if (!PreferencesUtil.isAdvancedUnlockEnable(context) if (!PreferencesUtil.isAdvancedUnlockEnable(retrieveContext())
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) { || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE) 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 // Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active) // and the activity still active)
if (advancedUnlockHelper?.isKeyManagerInitialized != true) { if (advancedUnlockHelper?.isKeyManagerInitialized != true) {
advancedUnlockHelper = AdvancedUnlockHelper(context) advancedUnlockHelper = AdvancedUnlockHelper(retrieveContext)
// callback for fingerprint findings // callback for fingerprint findings
advancedUnlockHelper?.advancedUnlockCallback = this advancedUnlockHelper?.advancedUnlockCallback = this
} }
@@ -109,7 +110,7 @@ class AdvancedUnlockManager(var context: FragmentActivity,
if (advancedUnlockHelper?.isKeyManagerInitialized != true) { if (advancedUnlockHelper?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE) toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else { } else {
if (builderListener.conditionToStoreCredential()) { if (builderListener?.conditionToStoreCredential() == true) {
// listen for encryption // listen for encryption
toggleMode(Mode.STORE_CREDENTIAL) toggleMode(Mode.STORE_CREDENTIAL)
} else { } else {
@@ -148,7 +149,7 @@ class AdvancedUnlockManager(var context: FragmentActivity,
private fun openBiometricSetting() { private fun openBiometricSetting() {
advancedUnlockInfoView.setIconViewClickListener(false) { advancedUnlockInfoView.setIconViewClickListener(false) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices... // 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) { advancedUnlockInfoView.setIconViewClickListener(false) {
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS, 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) { private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
context.runOnUiThread { retrieveContext().runOnUiThread {
if (allowOpenBiometricPrompt) { if (allowOpenBiometricPrompt) {
try { try {
advancedUnlockHelper advancedUnlockHelper
@@ -229,8 +230,8 @@ class AdvancedUnlockManager(var context: FragmentActivity,
} }
// Auto open the biometric prompt // Auto open the biometric prompt
if (isBiometricPromptAutoOpenEnable) { if (autoOpenPrompt) {
isBiometricPromptAutoOpenEnable = false autoOpenPrompt = false
openAdvancedUnlockPrompt(cryptoPrompt) openAdvancedUnlockPrompt(cryptoPrompt)
} }
} }
@@ -266,7 +267,7 @@ class AdvancedUnlockManager(var context: FragmentActivity,
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE && (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE) && biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
mAddBiometricMenuInProgress = false mAddBiometricMenuInProgress = false
context.invalidateOptionsMenu() retrieveContext().invalidateOptionsMenu()
} }
} }
} }
@@ -322,21 +323,21 @@ class AdvancedUnlockManager(var context: FragmentActivity,
} }
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
context.runOnUiThread { retrieveContext().runOnUiThread {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString") Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedMessageView(errString.toString()) setAdvancedUnlockedMessageView(errString.toString())
} }
} }
override fun onAuthenticationFailed() { override fun onAuthenticationFailed() {
context.runOnUiThread { retrieveContext().runOnUiThread {
Log.e(TAG, "Biometric authentication failed, biometric not recognized") Log.e(TAG, "Biometric authentication failed, biometric not recognized")
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized) setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
} }
} }
override fun onAuthenticationSucceeded() { override fun onAuthenticationSucceeded() {
context.runOnUiThread { retrieveContext().runOnUiThread {
when (biometricMode) { when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> { Mode.BIOMETRIC_UNAVAILABLE -> {
} }
@@ -350,8 +351,10 @@ class AdvancedUnlockManager(var context: FragmentActivity,
} }
Mode.STORE_CREDENTIAL -> { Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way // newly store the entered password in encrypted way
advancedUnlockHelper?.encryptData(builderListener.retrieveCredentialForEncryption()) builderListener?.retrieveCredentialForEncryption()?.let { credential ->
AdvancedUnlockNotificationService.startServiceForTimeout(context) advancedUnlockHelper?.encryptData(credential)
}
AdvancedUnlockNotificationService.startServiceForTimeout(retrieveContext())
} }
Mode.EXTRACT_CREDENTIAL -> { Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences // retrieve the encrypted value from preferences
@@ -369,14 +372,14 @@ class AdvancedUnlockManager(var context: FragmentActivity,
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) { override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
databaseFileUri?.let { databaseUri -> databaseFileUri?.let { databaseUri ->
builderListener.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec) builderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec)
} }
} }
override fun handleDecryptedResult(decryptedValue: String) { override fun handleDecryptedResult(decryptedValue: String) {
// Load database directly with password retrieve // Load database directly with password retrieve
databaseFileUri?.let { databaseFileUri?.let {
builderListener.onCredentialDecrypted(it, decryptedValue) builderListener?.onCredentialDecrypted(it, decryptedValue)
} }
} }
@@ -390,25 +393,25 @@ class AdvancedUnlockManager(var context: FragmentActivity,
} }
private fun showFingerPrintViews(show: Boolean) { private fun showFingerPrintViews(show: Boolean) {
context.runOnUiThread { retrieveContext().runOnUiThread {
advancedUnlockInfoView.visibility = if (show) View.VISIBLE else View.GONE advancedUnlockInfoView.visibility = if (show) View.VISIBLE else View.GONE
} }
} }
private fun setAdvancedUnlockedTitleView(textId: Int) { private fun setAdvancedUnlockedTitleView(textId: Int) {
context.runOnUiThread { retrieveContext().runOnUiThread {
advancedUnlockInfoView.setTitle(textId) advancedUnlockInfoView.setTitle(textId)
} }
} }
private fun setAdvancedUnlockedMessageView(textId: Int) { private fun setAdvancedUnlockedMessageView(textId: Int) {
context.runOnUiThread { retrieveContext().runOnUiThread {
advancedUnlockInfoView.setMessage(textId) advancedUnlockInfoView.setMessage(textId)
} }
} }
private fun setAdvancedUnlockedMessageView(text: CharSequence) { private fun setAdvancedUnlockedMessageView(text: CharSequence) {
context.runOnUiThread { retrieveContext().runOnUiThread {
advancedUnlockInfoView.message = text advancedUnlockInfoView.message = text
} }
} }

View File

@@ -53,23 +53,19 @@ object MenuUtil {
fun onDefaultMenuOptionsItemSelected(activity: Activity, fun onDefaultMenuOptionsItemSelected(activity: Activity,
item: MenuItem, item: MenuItem,
readOnly: Boolean = READ_ONLY_DEFAULT, readOnly: Boolean = READ_ONLY_DEFAULT,
timeoutEnable: Boolean = false): Boolean { timeoutEnable: Boolean = false) {
when (item.itemId) { when (item.itemId) {
R.id.menu_contribute -> { R.id.menu_contribute -> {
onContributionItemSelected(activity) onContributionItemSelected(activity)
return true
} }
R.id.menu_app_settings -> { R.id.menu_app_settings -> {
// To avoid flickering when launch settings in a LockingActivity // To avoid flickering when launch settings in a LockingActivity
SettingsActivity.launch(activity, readOnly, timeoutEnable) SettingsActivity.launch(activity, readOnly, timeoutEnable)
return true
} }
R.id.menu_about -> { R.id.menu_about -> {
val intent = Intent(activity, AboutActivity::class.java) val intent = Intent(activity, AboutActivity::class.java)
activity.startActivity(intent) activity.startActivity(intent)
return true
} }
else -> return true
} }
} }
} }

View File

@@ -68,17 +68,10 @@
android:padding="0dp" android:padding="0dp"
android:contentDescription="@string/about" android:contentDescription="@string/about"
android:src="@drawable/ic_launcher_foreground"/> android:src="@drawable/ic_launcher_foreground"/>
<FrameLayout <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_advanced_unlock_container_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent" />
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
android:id="@+id/biometric_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/colorPrimary"
android:visibility="gone"/>
</FrameLayout>
</FrameLayout> </FrameLayout>
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
android:id="@+id/advanced_unlock_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/colorPrimary"
android:visibility="gone"/>
</FrameLayout>