mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'develop' into feature/Merge_from
This commit is contained in:
@@ -26,14 +26,14 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.Menu
|
||||||
import android.view.KeyEvent.KEYCODE_ENTER
|
import android.view.MenuItem
|
||||||
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.Button
|
||||||
|
import android.widget.CompoundButton
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
@@ -44,10 +44,11 @@ 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
|
||||||
import com.kunzisoft.keepass.activities.helpers.*
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
|
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||||
|
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
|
||||||
import com.kunzisoft.keepass.autofill.AutofillComponent
|
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
||||||
@@ -55,11 +56,9 @@ import com.kunzisoft.keepass.database.element.Database
|
|||||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||||
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
||||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||||
import com.kunzisoft.keepass.model.MainCredential
|
import com.kunzisoft.keepass.model.*
|
||||||
import com.kunzisoft.keepass.model.RegisterInfo
|
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_DATABASE_KEY
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
|
||||||
@@ -68,7 +67,7 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
|
|||||||
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.KeyFileSelectionView
|
import com.kunzisoft.keepass.view.MainCredentialView
|
||||||
import com.kunzisoft.keepass.view.asError
|
import com.kunzisoft.keepass.view.asError
|
||||||
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
|
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
|
||||||
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
|
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
|
||||||
@@ -80,11 +79,8 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
// Views
|
// Views
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
private var filenameView: TextView? = null
|
private var filenameView: TextView? = null
|
||||||
private var passwordView: EditText? = null
|
private var mainCredentialView: MainCredentialView? = null
|
||||||
private var keyFileSelectionView: KeyFileSelectionView? = null
|
|
||||||
private var confirmButtonView: Button? = null
|
private var confirmButtonView: Button? = null
|
||||||
private var checkboxPasswordView: CompoundButton? = null
|
|
||||||
private var checkboxKeyFileView: CompoundButton? = null
|
|
||||||
private var infoContainerView: ViewGroup? = null
|
private var infoContainerView: ViewGroup? = null
|
||||||
private lateinit var coordinatorLayout: CoordinatorLayout
|
private lateinit var coordinatorLayout: CoordinatorLayout
|
||||||
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
|
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
|
||||||
@@ -94,7 +90,6 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
|
|
||||||
private var mDefaultDatabase: Boolean = false
|
private var mDefaultDatabase: Boolean = false
|
||||||
private var mDatabaseFileUri: Uri? = null
|
private var mDatabaseFileUri: Uri? = null
|
||||||
private var mDatabaseKeyFileUri: Uri? = null
|
|
||||||
|
|
||||||
private var mRememberKeyFile: Boolean = false
|
private var mRememberKeyFile: Boolean = false
|
||||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||||
@@ -118,12 +113,9 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
confirmButtonView = findViewById(R.id.activity_password_open_button)
|
|
||||||
filenameView = findViewById(R.id.filename)
|
filenameView = findViewById(R.id.filename)
|
||||||
passwordView = findViewById(R.id.password)
|
mainCredentialView = findViewById(R.id.activity_password_credentials)
|
||||||
keyFileSelectionView = findViewById(R.id.keyfile_selection)
|
confirmButtonView = findViewById(R.id.activity_password_open_button)
|
||||||
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
|
||||||
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
|
||||||
infoContainerView = findViewById(R.id.activity_password_info_container)
|
infoContainerView = findViewById(R.id.activity_password_info_container)
|
||||||
coordinatorLayout = findViewById(R.id.activity_password_coordinator_layout)
|
coordinatorLayout = findViewById(R.id.activity_password_coordinator_layout)
|
||||||
|
|
||||||
@@ -137,38 +129,16 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
|
mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
|
||||||
mExternalFileHelper?.buildOpenDocument { uri ->
|
mExternalFileHelper?.buildOpenDocument { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
mDatabaseKeyFileUri = uri
|
mainCredentialView?.populateKeyFileTextView(uri)
|
||||||
populateKeyFileTextView(uri)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
|
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
|
||||||
|
mainCredentialView?.onValidateListener = {
|
||||||
passwordView?.setOnEditorActionListener(onEditorActionListener)
|
loadDatabase()
|
||||||
passwordView?.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
|
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
|
||||||
if (editable.toString().isNotEmpty() && checkboxPasswordView?.isChecked != true)
|
|
||||||
checkboxPasswordView?.isChecked = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
passwordView?.setOnKeyListener { _, _, keyEvent ->
|
|
||||||
var handled = false
|
|
||||||
if (keyEvent.action == KeyEvent.ACTION_DOWN
|
|
||||||
&& keyEvent?.keyCode == KEYCODE_ENTER) {
|
|
||||||
verifyCheckboxesAndLoadDatabase()
|
|
||||||
handled = true
|
|
||||||
}
|
|
||||||
handled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If is a view intent
|
// If is a view intent
|
||||||
getUriFromIntent(intent)
|
getUriFromIntent(intent)
|
||||||
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
|
|
||||||
mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init Biometric elements
|
// Init Biometric elements
|
||||||
advancedUnlockFragment = supportFragmentManager
|
advancedUnlockFragment = supportFragmentManager
|
||||||
@@ -183,10 +153,11 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen password checkbox to init advanced unlock and confirmation button
|
// Listen password checkbox to init advanced unlock and confirmation button
|
||||||
checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
|
mainCredentialView?.onPasswordChecked =
|
||||||
mAdvancedUnlockViewModel.checkUnlockAvailability()
|
CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||||
enableOrNotTheConfirmationButton()
|
mAdvancedUnlockViewModel.checkUnlockAvailability()
|
||||||
}
|
enableConfirmationButton()
|
||||||
|
}
|
||||||
|
|
||||||
// Observe if default database
|
// Observe if default database
|
||||||
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
|
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
|
||||||
@@ -211,12 +182,13 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
// Post init uri with KeyFile only if needed
|
// Post init uri with KeyFile only if needed
|
||||||
|
val databaseKeyFileUri = mainCredentialView?.getMainCredential()?.keyFileUri
|
||||||
val keyFileUri =
|
val keyFileUri =
|
||||||
if (mRememberKeyFile
|
if (mRememberKeyFile
|
||||||
&& (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
|
&& (databaseKeyFileUri == null || databaseKeyFileUri.toString().isEmpty())) {
|
||||||
databaseFile?.keyFileUri
|
databaseFile?.keyFileUri
|
||||||
} else {
|
} else {
|
||||||
mDatabaseKeyFileUri
|
databaseKeyFileUri
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define title
|
// Define title
|
||||||
@@ -271,7 +243,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
launchGroupActivityIfLoaded(database)
|
launchGroupActivityIfLoaded(database)
|
||||||
} else {
|
} else {
|
||||||
passwordView?.requestFocusFromTouch()
|
mainCredentialView?.requestPasswordFocus()
|
||||||
|
|
||||||
var resultError = ""
|
var resultError = ""
|
||||||
val resultException = result.exception
|
val resultException = result.exception
|
||||||
@@ -288,7 +260,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
var databaseUri: Uri? = null
|
var databaseUri: Uri? = null
|
||||||
var mainCredential = MainCredential()
|
var mainCredential = MainCredential()
|
||||||
var readOnly = true
|
var readOnly = true
|
||||||
var cipherEntity: CipherDatabaseEntity? = null
|
var cipherEncryptDatabase: CipherEncryptDatabase? = null
|
||||||
|
|
||||||
result.data?.let { resultData ->
|
result.data?.let { resultData ->
|
||||||
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
|
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
|
||||||
@@ -296,8 +268,8 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
resultData.getParcelable(MAIN_CREDENTIAL_KEY)
|
resultData.getParcelable(MAIN_CREDENTIAL_KEY)
|
||||||
?: mainCredential
|
?: mainCredential
|
||||||
readOnly = resultData.getBoolean(READ_ONLY_KEY)
|
readOnly = resultData.getBoolean(READ_ONLY_KEY)
|
||||||
cipherEntity =
|
cipherEncryptDatabase =
|
||||||
resultData.getParcelable(CIPHER_ENTITY_KEY)
|
resultData.getParcelable(CIPHER_DATABASE_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
databaseUri?.let { databaseFileUri ->
|
databaseUri?.let { databaseFileUri ->
|
||||||
@@ -305,7 +277,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
databaseFileUri,
|
databaseFileUri,
|
||||||
mainCredential,
|
mainCredential,
|
||||||
readOnly,
|
readOnly,
|
||||||
cipherEntity,
|
cipherEncryptDatabase,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -341,11 +313,16 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
if (action != null
|
if (action != null
|
||||||
&& action == VIEW_INTENT) {
|
&& action == VIEW_INTENT) {
|
||||||
mDatabaseFileUri = intent.data
|
mDatabaseFileUri = intent.data
|
||||||
mDatabaseKeyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
|
mainCredentialView?.populateKeyFileTextView(UriUtil.getUriFromIntent(intent, KEY_KEYFILE))
|
||||||
} else {
|
} else {
|
||||||
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
|
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
|
||||||
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
|
intent?.getParcelableExtra<Uri?>(KEY_KEYFILE)?.let {
|
||||||
|
mainCredentialView?.populateKeyFileTextView(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
intent?.removeExtra(KEY_KEYFILE)
|
||||||
|
} catch (e: Exception) {}
|
||||||
mDatabaseFileUri?.let {
|
mDatabaseFileUri?.let {
|
||||||
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
|
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
|
||||||
}
|
}
|
||||||
@@ -380,51 +357,68 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun retrieveCredentialForEncryption(): String {
|
override fun retrieveCredentialForEncryption(): ByteArray {
|
||||||
return passwordView?.text?.toString() ?: ""
|
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
|
||||||
|
?: byteArrayOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun conditionToStoreCredential(): Boolean {
|
override fun conditionToStoreCredential(): Boolean {
|
||||||
return checkboxPasswordView?.isChecked == true
|
return mainCredentialView?.conditionToStoreCredential() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCredentialEncrypted(databaseUri: Uri,
|
override fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
|
||||||
encryptedCredential: String,
|
|
||||||
ivSpec: String) {
|
|
||||||
// Load the database if password is registered with biometric
|
// Load the database if password is registered with biometric
|
||||||
verifyCheckboxesAndLoadDatabase(
|
loadDatabase(mDatabaseFileUri,
|
||||||
CipherDatabaseEntity(
|
mainCredentialView?.getMainCredential(),
|
||||||
databaseUri.toString(),
|
cipherEncryptDatabase
|
||||||
encryptedCredential,
|
|
||||||
ivSpec)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCredentialDecrypted(databaseUri: Uri,
|
private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener {
|
||||||
decryptedCredential: String) {
|
override fun passwordToStore(password: String?): ByteArray? {
|
||||||
// Load the database if password is retrieve from biometric
|
return password?.toByteArray()
|
||||||
// Retrieve from biometric
|
}
|
||||||
verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential)
|
|
||||||
|
override fun keyfileToStore(keyfile: Uri?): ByteArray? {
|
||||||
|
// TODO create byte array to store keyfile
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hardwareKeyToStore(): ByteArray? {
|
||||||
|
// TODO create byte array to store hardware key
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onEditorActionListener = object : TextView.OnEditorActionListener {
|
override fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
|
||||||
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
// Load the database if password is retrieve from biometric
|
||||||
if (actionId == IME_ACTION_DONE) {
|
// Retrieve from biometric
|
||||||
verifyCheckboxesAndLoadDatabase()
|
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()
|
||||||
return true
|
when (cipherDecryptDatabase.credentialStorage) {
|
||||||
|
CredentialStorage.PASSWORD -> {
|
||||||
|
mainCredential.masterPassword = String(cipherDecryptDatabase.decryptedValue)
|
||||||
|
}
|
||||||
|
CredentialStorage.KEY_FILE -> {
|
||||||
|
// TODO advanced unlock key file
|
||||||
|
}
|
||||||
|
CredentialStorage.HARDWARE_KEY -> {
|
||||||
|
// TODO advanced unlock hardware key
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
loadDatabase(mDatabaseFileUri,
|
||||||
|
mainCredential,
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) {
|
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) {
|
||||||
// Define Key File text
|
// Define Key File text
|
||||||
if (mRememberKeyFile) {
|
if (mRememberKeyFile) {
|
||||||
populateKeyFileTextView(keyFileUri)
|
mainCredentialView?.populateKeyFileTextView(keyFileUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define listener for validate button
|
// Define listener for validate button
|
||||||
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
|
confirmButtonView?.setOnClickListener { loadDatabase() }
|
||||||
|
|
||||||
// If Activity is launch with a password and want to open directly
|
// If Activity is launch with a password and want to open directly
|
||||||
val intent = intent
|
val intent = intent
|
||||||
@@ -433,66 +427,33 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
intent.removeExtra(KEY_PASSWORD)
|
intent.removeExtra(KEY_PASSWORD)
|
||||||
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
|
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
populatePasswordTextView(password)
|
mainCredentialView?.populatePasswordTextView(password)
|
||||||
}
|
}
|
||||||
if (launchImmediately) {
|
if (launchImmediately) {
|
||||||
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
|
loadDatabase()
|
||||||
} else {
|
} else {
|
||||||
// Init Biometric elements
|
// Init Biometric elements
|
||||||
mAdvancedUnlockViewModel.databaseFileLoaded(databaseFileUri)
|
mAdvancedUnlockViewModel.databaseFileLoaded(databaseFileUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
enableOrNotTheConfirmationButton()
|
enableConfirmationButton()
|
||||||
|
|
||||||
// Auto select the password field and open keyboard
|
mainCredentialView?.focusPasswordFieldAndOpenKeyboard()
|
||||||
passwordView?.postDelayed({
|
|
||||||
passwordView?.requestFocusFromTouch()
|
|
||||||
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager?
|
|
||||||
inputMethodManager?.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableOrNotTheConfirmationButton() {
|
private fun enableConfirmationButton() {
|
||||||
// Enable or not the open button if setting is checked
|
// Enable or not the open button if setting is checked
|
||||||
if (!PreferencesUtil.emptyPasswordAllowed(this@PasswordActivity)) {
|
if (!PreferencesUtil.emptyPasswordAllowed(this@PasswordActivity)) {
|
||||||
checkboxPasswordView?.let {
|
confirmButtonView?.isEnabled = mainCredentialView?.isFill() ?: false
|
||||||
confirmButtonView?.isEnabled = (checkboxPasswordView?.isChecked == true
|
|
||||||
|| checkboxKeyFileView?.isChecked == true)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
confirmButtonView?.isEnabled = true
|
confirmButtonView?.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
|
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
|
||||||
populatePasswordTextView(null)
|
mainCredentialView?.populatePasswordTextView(null)
|
||||||
if (clearKeyFile) {
|
if (clearKeyFile) {
|
||||||
mDatabaseKeyFileUri = null
|
mainCredentialView?.populateKeyFileTextView(null)
|
||||||
populateKeyFileTextView(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun populatePasswordTextView(text: String?) {
|
|
||||||
if (text == null || text.isEmpty()) {
|
|
||||||
passwordView?.setText("")
|
|
||||||
if (checkboxPasswordView?.isChecked == true)
|
|
||||||
checkboxPasswordView?.isChecked = false
|
|
||||||
} else {
|
|
||||||
passwordView?.setText(text)
|
|
||||||
if (checkboxPasswordView?.isChecked != true)
|
|
||||||
checkboxPasswordView?.isChecked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun populateKeyFileTextView(uri: Uri?) {
|
|
||||||
if (uri == null || uri.toString().isEmpty()) {
|
|
||||||
keyFileSelectionView?.uri = null
|
|
||||||
if (checkboxKeyFileView?.isChecked == true)
|
|
||||||
checkboxKeyFileView?.isChecked = false
|
|
||||||
} else {
|
|
||||||
keyFileSelectionView?.uri = uri
|
|
||||||
if (checkboxKeyFileView?.isChecked != true)
|
|
||||||
checkboxKeyFileView?.isChecked = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,41 +465,20 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
mDatabaseKeyFileUri?.let {
|
|
||||||
outState.putString(KEY_KEYFILE, it.toString())
|
|
||||||
}
|
|
||||||
outState.putBoolean(KEY_READ_ONLY, mReadOnly)
|
outState.putBoolean(KEY_READ_ONLY, mReadOnly)
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
private fun loadDatabase() {
|
||||||
val password: String? = passwordView?.text?.toString()
|
loadDatabase(mDatabaseFileUri,
|
||||||
val keyFile: Uri? = keyFileSelectionView?.uri
|
mainCredentialView?.getMainCredential(),
|
||||||
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
|
null
|
||||||
}
|
)
|
||||||
|
|
||||||
private fun verifyCheckboxesAndLoadDatabase(password: String?,
|
|
||||||
keyFile: Uri?,
|
|
||||||
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
|
||||||
val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
|
|
||||||
verifyKeyFileCheckbox(keyFile)
|
|
||||||
loadDatabase(mDatabaseFileUri, keyPassword, mDatabaseKeyFileUri, cipherDatabaseEntity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
|
|
||||||
val keyFile: Uri? = keyFileSelectionView?.uri
|
|
||||||
verifyKeyFileCheckbox(keyFile)
|
|
||||||
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun verifyKeyFileCheckbox(keyFile: Uri?) {
|
|
||||||
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDatabase(databaseFileUri: Uri?,
|
private fun loadDatabase(databaseFileUri: Uri?,
|
||||||
password: String?,
|
mainCredential: MainCredential?,
|
||||||
keyFileUri: Uri?,
|
cipherEncryptDatabase: CipherEncryptDatabase?) {
|
||||||
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
|
||||||
|
|
||||||
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
|
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
|
||||||
clearCredentialsViews()
|
clearCredentialsViews()
|
||||||
@@ -556,11 +496,12 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
databaseFileUri?.let { databaseUri ->
|
databaseFileUri?.let { databaseUri ->
|
||||||
// Show the progress dialog and load the database
|
// Show the progress dialog and load the database
|
||||||
showProgressDialogAndLoadDatabase(
|
showProgressDialogAndLoadDatabase(
|
||||||
databaseUri,
|
databaseUri,
|
||||||
MainCredential(password, keyFileUri),
|
mainCredential ?: MainCredential(),
|
||||||
mReadOnly,
|
mReadOnly,
|
||||||
cipherDatabaseEntity,
|
cipherEncryptDatabase,
|
||||||
false)
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -568,14 +509,14 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
|
|||||||
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
|
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
|
||||||
mainCredential: MainCredential,
|
mainCredential: MainCredential,
|
||||||
readOnly: Boolean,
|
readOnly: Boolean,
|
||||||
cipherDatabaseEntity: CipherDatabaseEntity?,
|
cipherEncryptDatabase: CipherEncryptDatabase?,
|
||||||
fixDuplicateUUID: Boolean) {
|
fixDuplicateUUID: Boolean) {
|
||||||
loadDatabase(
|
loadDatabase(
|
||||||
databaseUri,
|
databaseUri,
|
||||||
mainCredential,
|
mainCredential,
|
||||||
readOnly,
|
readOnly,
|
||||||
cipherDatabaseEntity,
|
cipherEncryptDatabase,
|
||||||
fixDuplicateUUID
|
fixDuplicateUUID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
|
||||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.MainCredential
|
import com.kunzisoft.keepass.model.MainCredential
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||||
@@ -59,9 +59,9 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
|||||||
fun loadDatabase(databaseUri: Uri,
|
fun loadDatabase(databaseUri: Uri,
|
||||||
mainCredential: MainCredential,
|
mainCredential: MainCredential,
|
||||||
readOnly: Boolean,
|
readOnly: Boolean,
|
||||||
cipherEntity: CipherDatabaseEntity?,
|
cipherEncryptDatabase: CipherEncryptDatabase?,
|
||||||
fixDuplicateUuid: Boolean) {
|
fixDuplicateUuid: Boolean) {
|
||||||
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEntity, fixDuplicateUuid)
|
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEncryptDatabase, fixDuplicateUuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun closeDatabase() {
|
protected fun closeDatabase() {
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ package com.kunzisoft.keepass.app.database
|
|||||||
import android.content.*
|
import android.content.*
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
|
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.SingletonHolderParameter
|
import com.kunzisoft.keepass.utils.SingletonHolderParameter
|
||||||
@@ -125,15 +127,26 @@ class CipherDatabaseAction(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getCipherDatabase(databaseUri: Uri,
|
fun getCipherDatabase(databaseUri: Uri,
|
||||||
cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) {
|
cipherDatabaseResultListener: (CipherEncryptDatabase?) -> Unit) {
|
||||||
if (useTempDao) {
|
if (useTempDao) {
|
||||||
serviceActionTask {
|
serviceActionTask {
|
||||||
cipherDatabaseResultListener.invoke(mBinder?.getCipherDatabase(databaseUri))
|
val cipherDatabaseEntity = mBinder?.getCipherDatabase(databaseUri)
|
||||||
|
val cipherDatabase = CipherEncryptDatabase().apply {
|
||||||
|
this.databaseUri = Uri.parse(cipherDatabaseEntity?.databaseUri)
|
||||||
|
this.encryptedValue = Base64.decode(cipherDatabaseEntity?.encryptedValue, Base64.NO_WRAP)
|
||||||
|
this.specParameters = Base64.decode(cipherDatabaseEntity?.specParameters, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
cipherDatabaseResultListener.invoke(cipherDatabase)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
IOActionTask(
|
IOActionTask(
|
||||||
{
|
{
|
||||||
cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
|
val cipherDatabaseEntity = cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
|
||||||
|
CipherEncryptDatabase().apply {
|
||||||
|
this.databaseUri = Uri.parse(cipherDatabaseEntity?.databaseUri)
|
||||||
|
this.encryptedValue = Base64.decode(cipherDatabaseEntity?.encryptedValue, Base64.NO_WRAP)
|
||||||
|
this.specParameters = Base64.decode(cipherDatabaseEntity?.specParameters, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cipherDatabaseResultListener.invoke(it)
|
cipherDatabaseResultListener.invoke(it)
|
||||||
@@ -149,18 +162,27 @@ class CipherDatabaseAction(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity,
|
fun addOrUpdateCipherDatabase(cipherEncryptDatabase: CipherEncryptDatabase,
|
||||||
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
||||||
if (useTempDao) {
|
cipherEncryptDatabase.databaseUri?.let { databaseUri ->
|
||||||
// The only case to create service (not needed to get an info)
|
|
||||||
serviceActionTask(true) {
|
val cipherDatabaseEntity = CipherDatabaseEntity(
|
||||||
mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
|
databaseUri.toString(),
|
||||||
cipherDatabaseResultListener?.invoke()
|
Base64.encodeToString(cipherEncryptDatabase.encryptedValue, Base64.NO_WRAP),
|
||||||
}
|
Base64.encodeToString(cipherEncryptDatabase.specParameters, Base64.NO_WRAP),
|
||||||
} else {
|
)
|
||||||
IOActionTask(
|
|
||||||
|
if (useTempDao) {
|
||||||
|
// The only case to create service (not needed to get an info)
|
||||||
|
serviceActionTask(true) {
|
||||||
|
mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
|
||||||
|
cipherDatabaseResultListener?.invoke()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IOActionTask(
|
||||||
{
|
{
|
||||||
val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
|
val cipherDatabaseRetrieve =
|
||||||
|
cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
|
||||||
// Update values if element not yet in the database
|
// Update values if element not yet in the database
|
||||||
if (cipherDatabaseRetrieve == null) {
|
if (cipherDatabaseRetrieve == null) {
|
||||||
cipherDatabaseDao.add(cipherDatabaseEntity)
|
cipherDatabaseDao.add(cipherDatabaseEntity)
|
||||||
@@ -171,7 +193,8 @@ class CipherDatabaseAction(context: Context) {
|
|||||||
{
|
{
|
||||||
cipherDatabaseResultListener?.invoke()
|
cipherDatabaseResultListener?.invoke()
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
|||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||||
import com.kunzisoft.keepass.database.exception.IODatabaseException
|
import com.kunzisoft.keepass.database.exception.IODatabaseException
|
||||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||||
|
import com.kunzisoft.keepass.model.CipherDecryptDatabase
|
||||||
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
|
import com.kunzisoft.keepass.model.CredentialStorage
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||||
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
|
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
|
||||||
@@ -60,6 +63,9 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
var databaseFileUri: Uri? = null
|
var databaseFileUri: Uri? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
// TODO Retrieve credential storage from app database
|
||||||
|
var credentialDatabaseStorage: CredentialStorage = CredentialStorage.DEFAULT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage setting to auto open biometric prompt
|
* Manage setting to auto open biometric prompt
|
||||||
*/
|
*/
|
||||||
@@ -477,6 +483,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
} ?: checkUnlockAvailability()
|
} ?: checkUnlockAvailability()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||||
@@ -528,16 +535,29 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
|
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {
|
||||||
databaseFileUri?.let { databaseUri ->
|
databaseFileUri?.let { databaseUri ->
|
||||||
mBuilderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec)
|
mBuilderListener?.onCredentialEncrypted(
|
||||||
|
CipherEncryptDatabase().apply {
|
||||||
|
this.databaseUri = databaseUri
|
||||||
|
this.credentialStorage = credentialDatabaseStorage
|
||||||
|
this.encryptedValue = encryptedValue
|
||||||
|
this.specParameters = ivSpec
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleDecryptedResult(decryptedValue: String) {
|
override fun handleDecryptedResult(decryptedValue: ByteArray) {
|
||||||
// Load database directly with password retrieve
|
// Load database directly with password retrieve
|
||||||
databaseFileUri?.let {
|
databaseFileUri?.let { databaseUri ->
|
||||||
mBuilderListener?.onCredentialDecrypted(it, decryptedValue)
|
mBuilderListener?.onCredentialDecrypted(
|
||||||
|
CipherDecryptDatabase().apply {
|
||||||
|
this.databaseUri = databaseUri
|
||||||
|
this.credentialStorage = credentialDatabaseStorage
|
||||||
|
this.decryptedValue = decryptedValue
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,6 +571,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
|
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
override fun onGenericException(e: Exception) {
|
override fun onGenericException(e: Exception) {
|
||||||
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
|
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
|
||||||
setAdvancedUnlockedMessageView(errorMessage)
|
setAdvancedUnlockedMessageView(errorMessage)
|
||||||
@@ -580,6 +601,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
|
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
mAdvancedUnlockInfoView?.message = text
|
mAdvancedUnlockInfoView?.message = text
|
||||||
@@ -617,10 +639,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface BuilderListener {
|
interface BuilderListener {
|
||||||
fun retrieveCredentialForEncryption(): String
|
fun retrieveCredentialForEncryption(): ByteArray
|
||||||
fun conditionToStoreCredential(): Boolean
|
fun conditionToStoreCredential(): Boolean
|
||||||
fun onCredentialEncrypted(databaseUri: Uri, encryptedCredential: String, ivSpec: String)
|
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase)
|
||||||
fun onCredentialDecrypted(databaseUri: Uri, decryptedCredential: String)
|
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import android.os.Build
|
|||||||
import android.security.keystore.KeyGenParameterSpec
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import android.util.Base64
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
@@ -214,18 +213,15 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encryptData(value: String) {
|
fun encryptData(value: ByteArray) {
|
||||||
if (!isKeyManagerInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val encrypted = cipher?.doFinal(value.toByteArray())
|
val encrypted = cipher?.doFinal(value) ?: byteArrayOf()
|
||||||
val encryptedBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
|
||||||
|
|
||||||
// passes updated iv spec on to callback so this can be stored for decryption
|
// passes updated iv spec on to callback so this can be stored for decryption
|
||||||
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
|
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
|
||||||
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
|
advancedUnlockCallback?.handleEncryptedResult(encrypted, spec.iv)
|
||||||
advancedUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Unable to encrypt data", e)
|
Log.e(TAG, "Unable to encrypt data", e)
|
||||||
@@ -233,12 +229,12 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initDecryptData(ivSpecValue: String,
|
fun initDecryptData(ivSpecValue: ByteArray,
|
||||||
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
|
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
|
||||||
initDecryptData(ivSpecValue, actionIfCypherInit, true)
|
initDecryptData(ivSpecValue, actionIfCypherInit, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initDecryptData(ivSpecValue: String,
|
private fun initDecryptData(ivSpecValue: ByteArray,
|
||||||
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
|
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
|
||||||
firstLaunch: Boolean = true) {
|
firstLaunch: Boolean = true) {
|
||||||
if (!isKeyManagerInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
@@ -246,9 +242,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// important to restore spec here that was used for decryption
|
// important to restore spec here that was used for decryption
|
||||||
val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP)
|
val spec = IvParameterSpec(ivSpecValue)
|
||||||
val spec = IvParameterSpec(iv)
|
|
||||||
|
|
||||||
getSecretKey()?.let { secretKey ->
|
getSecretKey()?.let { secretKey ->
|
||||||
cipher?.let { cipher ->
|
cipher?.let { cipher ->
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
||||||
@@ -284,15 +278,14 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decryptData(encryptedValue: String) {
|
fun decryptData(encryptedValue: ByteArray) {
|
||||||
if (!isKeyManagerInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// actual decryption here
|
// actual decryption here
|
||||||
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
|
cipher?.doFinal(encryptedValue)?.let { decrypted ->
|
||||||
cipher?.doFinal(encrypted)?.let { decrypted ->
|
advancedUnlockCallback?.handleDecryptedResult(decrypted)
|
||||||
advancedUnlockCallback?.handleDecryptedResult(String(decrypted))
|
|
||||||
}
|
}
|
||||||
} catch (badPaddingException: BadPaddingException) {
|
} catch (badPaddingException: BadPaddingException) {
|
||||||
Log.e(TAG, "Unable to decrypt data", badPaddingException)
|
Log.e(TAG, "Unable to decrypt data", badPaddingException)
|
||||||
@@ -367,8 +360,8 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
|
|||||||
fun onAuthenticationSucceeded()
|
fun onAuthenticationSucceeded()
|
||||||
fun onAuthenticationFailed()
|
fun onAuthenticationFailed()
|
||||||
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
|
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
|
||||||
fun handleEncryptedResult(encryptedValue: String, ivSpec: String)
|
fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray)
|
||||||
fun handleDecryptedResult(decryptedValue: String)
|
fun handleDecryptedResult(decryptedValue: ByteArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -469,9 +462,9 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
|
|||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
|
||||||
|
|
||||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
|
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {}
|
||||||
|
|
||||||
override fun handleDecryptedResult(decryptedValue: String) {}
|
override fun handleDecryptedResult(decryptedValue: ByteArray) {}
|
||||||
|
|
||||||
override fun onUnrecoverableKeyException(e: Exception) {
|
override fun onUnrecoverableKeyException(e: Exception) {
|
||||||
advancedCallback.onUnrecoverableKeyException(e)
|
advancedCallback.onUnrecoverableKeyException(e)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
|
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
|
||||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
@@ -43,6 +42,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
|||||||
import com.kunzisoft.keepass.database.element.node.Node
|
import com.kunzisoft.keepass.database.element.node.Node
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.MainCredential
|
import com.kunzisoft.keepass.model.MainCredential
|
||||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||||
@@ -343,13 +343,13 @@ class DatabaseTaskProvider {
|
|||||||
fun startDatabaseLoad(databaseUri: Uri,
|
fun startDatabaseLoad(databaseUri: Uri,
|
||||||
mainCredential: MainCredential,
|
mainCredential: MainCredential,
|
||||||
readOnly: Boolean,
|
readOnly: Boolean,
|
||||||
cipherEntity: CipherDatabaseEntity?,
|
cipherEncryptDatabase: CipherEncryptDatabase?,
|
||||||
fixDuplicateUuid: Boolean) {
|
fixDuplicateUuid: Boolean) {
|
||||||
start(Bundle().apply {
|
start(Bundle().apply {
|
||||||
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||||
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
|
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
|
||||||
putBoolean(DatabaseTaskNotificationService.READ_ONLY_KEY, readOnly)
|
putBoolean(DatabaseTaskNotificationService.READ_ONLY_KEY, readOnly)
|
||||||
putParcelable(DatabaseTaskNotificationService.CIPHER_ENTITY_KEY, cipherEntity)
|
putParcelable(DatabaseTaskNotificationService.CIPHER_DATABASE_KEY, cipherEncryptDatabase)
|
||||||
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
|
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
|
||||||
}
|
}
|
||||||
, ACTION_DATABASE_LOAD_TASK)
|
, ACTION_DATABASE_LOAD_TASK)
|
||||||
|
|||||||
@@ -22,12 +22,11 @@ package com.kunzisoft.keepass.database.action
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||||
import com.kunzisoft.keepass.database.element.binary.LoadedKey
|
|
||||||
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
||||||
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.MainCredential
|
import com.kunzisoft.keepass.model.MainCredential
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
@@ -39,7 +38,7 @@ class LoadDatabaseRunnable(private val context: Context,
|
|||||||
private val mUri: Uri,
|
private val mUri: Uri,
|
||||||
private val mMainCredential: MainCredential,
|
private val mMainCredential: MainCredential,
|
||||||
private val mReadonly: Boolean,
|
private val mReadonly: Boolean,
|
||||||
private val mCipherEntity: CipherDatabaseEntity?,
|
private val mCipherEncryptDatabase: CipherEncryptDatabase?,
|
||||||
private val mFixDuplicateUUID: Boolean,
|
private val mFixDuplicateUUID: Boolean,
|
||||||
private val progressTaskUpdater: ProgressTaskUpdater?,
|
private val progressTaskUpdater: ProgressTaskUpdater?,
|
||||||
private val mLoadDatabaseResult: ((Result) -> Unit)?)
|
private val mLoadDatabaseResult: ((Result) -> Unit)?)
|
||||||
@@ -76,9 +75,9 @@ class LoadDatabaseRunnable(private val context: Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register the biometric
|
// Register the biometric
|
||||||
mCipherEntity?.let { cipherDatabaseEntity ->
|
mCipherEncryptDatabase?.let { cipherDatabase ->
|
||||||
CipherDatabaseAction.getInstance(context)
|
CipherDatabaseAction.getInstance(context)
|
||||||
.addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
|
.addOrUpdateCipherDatabase(cipherDatabase) // return value not called
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the current time to init the lock timer
|
// Register the current time to init the lock timer
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.model
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.kunzisoft.keepass.utils.readEnum
|
||||||
|
import com.kunzisoft.keepass.utils.writeEnum
|
||||||
|
|
||||||
|
class CipherDecryptDatabase(): Parcelable {
|
||||||
|
|
||||||
|
var databaseUri: Uri? = null
|
||||||
|
var credentialStorage: CredentialStorage = CredentialStorage.DEFAULT
|
||||||
|
var decryptedValue: ByteArray = byteArrayOf()
|
||||||
|
|
||||||
|
constructor(parcel: Parcel): this() {
|
||||||
|
databaseUri = parcel.readParcelable(Uri::class.java.classLoader)
|
||||||
|
credentialStorage = parcel.readEnum<CredentialStorage>() ?: credentialStorage
|
||||||
|
decryptedValue = ByteArray(parcel.readInt())
|
||||||
|
parcel.readByteArray(decryptedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceContent(copy: CipherDecryptDatabase) {
|
||||||
|
this.decryptedValue = copy.decryptedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeParcelable(databaseUri, flags)
|
||||||
|
parcel.writeEnum(credentialStorage)
|
||||||
|
parcel.writeInt(decryptedValue.size)
|
||||||
|
parcel.writeByteArray(decryptedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<CipherDecryptDatabase> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): CipherDecryptDatabase {
|
||||||
|
return CipherDecryptDatabase(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<CipherDecryptDatabase?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as CipherDecryptDatabase
|
||||||
|
|
||||||
|
if (databaseUri != other.databaseUri) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return databaseUri.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.model
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.kunzisoft.keepass.utils.readEnum
|
||||||
|
import com.kunzisoft.keepass.utils.writeEnum
|
||||||
|
|
||||||
|
class CipherEncryptDatabase(): Parcelable {
|
||||||
|
|
||||||
|
var databaseUri: Uri? = null
|
||||||
|
var credentialStorage: CredentialStorage = CredentialStorage.DEFAULT
|
||||||
|
var encryptedValue: ByteArray = byteArrayOf()
|
||||||
|
var specParameters: ByteArray = byteArrayOf()
|
||||||
|
|
||||||
|
constructor(parcel: Parcel): this() {
|
||||||
|
databaseUri = parcel.readParcelable(Uri::class.java.classLoader)
|
||||||
|
credentialStorage = parcel.readEnum<CredentialStorage>() ?: credentialStorage
|
||||||
|
encryptedValue = ByteArray(parcel.readInt())
|
||||||
|
parcel.readByteArray(encryptedValue)
|
||||||
|
specParameters = ByteArray(parcel.readInt())
|
||||||
|
parcel.readByteArray(specParameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceContent(copy: CipherEncryptDatabase) {
|
||||||
|
this.encryptedValue = copy.encryptedValue
|
||||||
|
this.specParameters = copy.specParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeParcelable(databaseUri, flags)
|
||||||
|
parcel.writeEnum(credentialStorage)
|
||||||
|
parcel.writeInt(encryptedValue.size)
|
||||||
|
parcel.writeByteArray(encryptedValue)
|
||||||
|
parcel.writeInt(specParameters.size)
|
||||||
|
parcel.writeByteArray(specParameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<CipherEncryptDatabase> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): CipherEncryptDatabase {
|
||||||
|
return CipherEncryptDatabase(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<CipherEncryptDatabase?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as CipherEncryptDatabase
|
||||||
|
|
||||||
|
if (databaseUri != other.databaseUri) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return databaseUri.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.model
|
||||||
|
|
||||||
|
enum class CredentialStorage {
|
||||||
|
PASSWORD, KEY_FILE, HARDWARE_KEY;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getFromOrdinal(ordinal: Int): CredentialStorage {
|
||||||
|
return when (ordinal) {
|
||||||
|
0 -> PASSWORD
|
||||||
|
1 -> KEY_FILE
|
||||||
|
2 -> HARDWARE_KEY
|
||||||
|
else -> DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val DEFAULT: CredentialStorage
|
||||||
|
get() = PASSWORD
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,6 @@ import android.util.Log
|
|||||||
import androidx.media.app.NotificationCompat
|
import androidx.media.app.NotificationCompat
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.GroupActivity
|
import com.kunzisoft.keepass.activities.GroupActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
|
||||||
import com.kunzisoft.keepass.database.action.*
|
import com.kunzisoft.keepass.database.action.*
|
||||||
import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable
|
import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable
|
||||||
import com.kunzisoft.keepass.database.action.history.RestoreEntryHistoryDatabaseRunnable
|
import com.kunzisoft.keepass.database.action.history.RestoreEntryHistoryDatabaseRunnable
|
||||||
@@ -42,6 +41,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
|||||||
import com.kunzisoft.keepass.database.element.node.Node
|
import com.kunzisoft.keepass.database.element.node.Node
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
|
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||||
import com.kunzisoft.keepass.model.MainCredential
|
import com.kunzisoft.keepass.model.MainCredential
|
||||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
@@ -474,7 +474,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
intent?.removeExtra(DATABASE_URI_KEY)
|
intent?.removeExtra(DATABASE_URI_KEY)
|
||||||
intent?.removeExtra(MAIN_CREDENTIAL_KEY)
|
intent?.removeExtra(MAIN_CREDENTIAL_KEY)
|
||||||
intent?.removeExtra(READ_ONLY_KEY)
|
intent?.removeExtra(READ_ONLY_KEY)
|
||||||
intent?.removeExtra(CIPHER_ENTITY_KEY)
|
intent?.removeExtra(CIPHER_DATABASE_KEY)
|
||||||
intent?.removeExtra(FIX_DUPLICATE_UUID_KEY)
|
intent?.removeExtra(FIX_DUPLICATE_UUID_KEY)
|
||||||
intent?.removeExtra(GROUP_KEY)
|
intent?.removeExtra(GROUP_KEY)
|
||||||
intent?.removeExtra(ENTRY_KEY)
|
intent?.removeExtra(ENTRY_KEY)
|
||||||
@@ -575,13 +575,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
if (intent.hasExtra(DATABASE_URI_KEY)
|
if (intent.hasExtra(DATABASE_URI_KEY)
|
||||||
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
||||||
&& intent.hasExtra(READ_ONLY_KEY)
|
&& intent.hasExtra(READ_ONLY_KEY)
|
||||||
&& intent.hasExtra(CIPHER_ENTITY_KEY)
|
&& intent.hasExtra(CIPHER_DATABASE_KEY)
|
||||||
&& intent.hasExtra(FIX_DUPLICATE_UUID_KEY)
|
&& intent.hasExtra(FIX_DUPLICATE_UUID_KEY)
|
||||||
) {
|
) {
|
||||||
val databaseUri: Uri? = intent.getParcelableExtra(DATABASE_URI_KEY)
|
val databaseUri: Uri? = intent.getParcelableExtra(DATABASE_URI_KEY)
|
||||||
val mainCredential: MainCredential = intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
|
val mainCredential: MainCredential = intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
|
||||||
val readOnly: Boolean = intent.getBooleanExtra(READ_ONLY_KEY, true)
|
val readOnly: Boolean = intent.getBooleanExtra(READ_ONLY_KEY, true)
|
||||||
val cipherEntity: CipherDatabaseEntity? = intent.getParcelableExtra(CIPHER_ENTITY_KEY)
|
val cipherEncryptDatabase: CipherEncryptDatabase? = intent.getParcelableExtra(CIPHER_DATABASE_KEY)
|
||||||
|
|
||||||
if (databaseUri == null)
|
if (databaseUri == null)
|
||||||
return null
|
return null
|
||||||
@@ -594,7 +594,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
databaseUri,
|
databaseUri,
|
||||||
mainCredential,
|
mainCredential,
|
||||||
readOnly,
|
readOnly,
|
||||||
cipherEntity,
|
cipherEncryptDatabase,
|
||||||
intent.getBooleanExtra(FIX_DUPLICATE_UUID_KEY, false),
|
intent.getBooleanExtra(FIX_DUPLICATE_UUID_KEY, false),
|
||||||
this
|
this
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -603,7 +603,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
putParcelable(DATABASE_URI_KEY, databaseUri)
|
putParcelable(DATABASE_URI_KEY, databaseUri)
|
||||||
putParcelable(MAIN_CREDENTIAL_KEY, mainCredential)
|
putParcelable(MAIN_CREDENTIAL_KEY, mainCredential)
|
||||||
putBoolean(READ_ONLY_KEY, readOnly)
|
putBoolean(READ_ONLY_KEY, readOnly)
|
||||||
putParcelable(CIPHER_ENTITY_KEY, cipherEntity)
|
putParcelable(CIPHER_DATABASE_KEY, cipherEncryptDatabase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -981,7 +981,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
|
const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
|
||||||
const val MAIN_CREDENTIAL_KEY = "MAIN_CREDENTIAL_KEY"
|
const val MAIN_CREDENTIAL_KEY = "MAIN_CREDENTIAL_KEY"
|
||||||
const val READ_ONLY_KEY = "READ_ONLY_KEY"
|
const val READ_ONLY_KEY = "READ_ONLY_KEY"
|
||||||
const val CIPHER_ENTITY_KEY = "CIPHER_ENTITY_KEY"
|
const val CIPHER_DATABASE_KEY = "CIPHER_DATABASE_KEY"
|
||||||
const val FIX_DUPLICATE_UUID_KEY = "FIX_DUPLICATE_UUID_KEY"
|
const val FIX_DUPLICATE_UUID_KEY = "FIX_DUPLICATE_UUID_KEY"
|
||||||
const val GROUP_KEY = "GROUP_KEY"
|
const val GROUP_KEY = "GROUP_KEY"
|
||||||
const val ENTRY_KEY = "ENTRY_KEY"
|
const val ENTRY_KEY = "ENTRY_KEY"
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.view
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.*
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||||
|
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||||
|
import com.kunzisoft.keepass.model.MainCredential
|
||||||
|
import com.kunzisoft.keepass.model.CredentialStorage
|
||||||
|
|
||||||
|
class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyle: Int = 0)
|
||||||
|
: FrameLayout(context, attrs, defStyle) {
|
||||||
|
|
||||||
|
private var passwordView: EditText
|
||||||
|
private var keyFileSelectionView: KeyFileSelectionView
|
||||||
|
private var checkboxPasswordView: CompoundButton
|
||||||
|
private var checkboxKeyFileView: CompoundButton
|
||||||
|
|
||||||
|
var onPasswordChecked: (CompoundButton.OnCheckedChangeListener)? = null
|
||||||
|
var onValidateListener: (() -> Unit)? = null
|
||||||
|
|
||||||
|
private var mCredentialStorage: CredentialStorage = CredentialStorage.PASSWORD
|
||||||
|
|
||||||
|
init {
|
||||||
|
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||||
|
inflater?.inflate(R.layout.view_credentials, this)
|
||||||
|
|
||||||
|
passwordView = findViewById(R.id.password)
|
||||||
|
keyFileSelectionView = findViewById(R.id.keyfile_selection)
|
||||||
|
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
||||||
|
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
||||||
|
|
||||||
|
val onEditorActionListener = object : TextView.OnEditorActionListener {
|
||||||
|
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
onValidateListener?.invoke()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordView.setOnEditorActionListener(onEditorActionListener)
|
||||||
|
passwordView.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
|
|
||||||
|
override fun afterTextChanged(editable: Editable) {
|
||||||
|
if (editable.toString().isNotEmpty() && !checkboxPasswordView.isChecked)
|
||||||
|
checkboxPasswordView.isChecked = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
passwordView.setOnKeyListener { _, _, keyEvent ->
|
||||||
|
var handled = false
|
||||||
|
if (keyEvent.action == KeyEvent.ACTION_DOWN
|
||||||
|
&& keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
|
||||||
|
) {
|
||||||
|
onValidateListener?.invoke()
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
handled
|
||||||
|
}
|
||||||
|
|
||||||
|
checkboxPasswordView.setOnCheckedChangeListener { view, checked ->
|
||||||
|
onPasswordChecked?.onCheckedChanged(view, checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOpenKeyfileClickListener(externalFileHelper: ExternalFileHelper?) {
|
||||||
|
keyFileSelectionView.setOpenDocumentClickListener(externalFileHelper)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun populatePasswordTextView(text: String?) {
|
||||||
|
if (text == null || text.isEmpty()) {
|
||||||
|
passwordView.setText("")
|
||||||
|
if (checkboxPasswordView.isChecked)
|
||||||
|
checkboxPasswordView.isChecked = false
|
||||||
|
} else {
|
||||||
|
passwordView.setText(text)
|
||||||
|
if (checkboxPasswordView.isChecked)
|
||||||
|
checkboxPasswordView.isChecked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun populateKeyFileTextView(uri: Uri?) {
|
||||||
|
if (uri == null || uri.toString().isEmpty()) {
|
||||||
|
keyFileSelectionView.uri = null
|
||||||
|
if (checkboxKeyFileView.isChecked)
|
||||||
|
checkboxKeyFileView.isChecked = false
|
||||||
|
} else {
|
||||||
|
keyFileSelectionView.uri = uri
|
||||||
|
if (!checkboxKeyFileView.isChecked)
|
||||||
|
checkboxKeyFileView.isChecked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isFill(): Boolean {
|
||||||
|
return checkboxPasswordView.isChecked || checkboxKeyFileView.isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMainCredential(): MainCredential {
|
||||||
|
return MainCredential().apply {
|
||||||
|
this.masterPassword = if (checkboxPasswordView.isChecked)
|
||||||
|
passwordView.text?.toString() else null
|
||||||
|
this.keyFileUri = if (checkboxKeyFileView.isChecked)
|
||||||
|
keyFileSelectionView.uri else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeConditionToStoreCredential(credentialStorage: CredentialStorage) {
|
||||||
|
this.mCredentialStorage = credentialStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
fun conditionToStoreCredential(): Boolean {
|
||||||
|
// TODO HARDWARE_KEY
|
||||||
|
return when (mCredentialStorage) {
|
||||||
|
CredentialStorage.PASSWORD -> checkboxPasswordView.isChecked
|
||||||
|
CredentialStorage.KEY_FILE -> checkboxPasswordView.isChecked
|
||||||
|
CredentialStorage.HARDWARE_KEY -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return content of the store credential view allowed,
|
||||||
|
* String? for password
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun retrieveCredentialForStorage(listener: CredentialStorageListener): ByteArray? {
|
||||||
|
return when (mCredentialStorage) {
|
||||||
|
CredentialStorage.PASSWORD -> listener.passwordToStore(passwordView.text?.toString())
|
||||||
|
CredentialStorage.KEY_FILE -> listener.keyfileToStore(keyFileSelectionView.uri)
|
||||||
|
CredentialStorage.HARDWARE_KEY -> listener.hardwareKeyToStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CredentialStorageListener {
|
||||||
|
fun passwordToStore(password: String?): ByteArray?
|
||||||
|
fun keyfileToStore(keyfile: Uri?): ByteArray?
|
||||||
|
fun hardwareKeyToStore(): ByteArray?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestPasswordFocus() {
|
||||||
|
passwordView.requestFocusFromTouch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto select the password field and open keyboard
|
||||||
|
fun focusPasswordFieldAndOpenKeyboard() {
|
||||||
|
passwordView.postDelayed({
|
||||||
|
passwordView.requestFocusFromTouch()
|
||||||
|
val inputMethodManager = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as? InputMethodManager?
|
||||||
|
inputMethodManager?.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(): Parcelable {
|
||||||
|
val superState = super.onSaveInstanceState()
|
||||||
|
val saveState = SavedState(superState)
|
||||||
|
saveState.mCredentialStorage = this.mCredentialStorage
|
||||||
|
return saveState
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||||
|
if (state !is SavedState) {
|
||||||
|
super.onRestoreInstanceState(state)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
super.onRestoreInstanceState(state.superState)
|
||||||
|
this.mCredentialStorage = state.mCredentialStorage ?: CredentialStorage.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SavedState : BaseSavedState {
|
||||||
|
var mCredentialStorage: CredentialStorage? = null
|
||||||
|
|
||||||
|
constructor(superState: Parcelable?) : super(superState) {}
|
||||||
|
|
||||||
|
private constructor(parcel: Parcel) : super(parcel) {
|
||||||
|
mCredentialStorage = CredentialStorage.getFromOrdinal(parcel.readInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||||
|
super.writeToParcel(out, flags)
|
||||||
|
out.writeInt(mCredentialStorage?.ordinal ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<SavedState> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): SavedState {
|
||||||
|
return SavedState(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<SavedState?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,102 +107,20 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
android:id="@+id/unlock_container"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
android:elevation="4dp"
|
android:elevation="4dp"
|
||||||
android:paddingTop="6dp"
|
|
||||||
android:paddingLeft="12dp"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingRight="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:paddingBottom="12dp"
|
|
||||||
android:background="?android:attr/windowBackground"
|
|
||||||
app:layout_constraintWidth_percent="@dimen/content_percent"
|
app:layout_constraintWidth_percent="@dimen/content_percent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
<com.kunzisoft.keepass.view.MainCredentialView
|
||||||
<!-- Password Input -->
|
android:id="@+id/activity_password_credentials"
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/password_input_container"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
|
||||||
android:id="@+id/password_checkbox"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignTop="@+id/password_input_layout"
|
|
||||||
android:layout_marginTop="22dp"
|
|
||||||
android:contentDescription="@string/content_description_password_checkbox"
|
|
||||||
android:focusable="false"
|
|
||||||
android:gravity="center_vertical" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/password_input_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_toRightOf="@+id/password_checkbox"
|
|
||||||
android:layout_toEndOf="@+id/password_checkbox"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
app:endIconMode="password_toggle"
|
|
||||||
app:endIconTint="?attr/colorAccent">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:hint="@string/password"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:importantForAutofill="yes"
|
|
||||||
android:focusable="true"
|
|
||||||
android:focusableInTouchMode="true"
|
|
||||||
android:autofillHints="password"
|
|
||||||
android:imeOptions="actionDone|flagNoPersonalizedLearning"
|
|
||||||
android:maxLines="1"/>
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<!-- File Input -->
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/container_key_file"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content" />
|
||||||
|
</FrameLayout>
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
|
||||||
android:id="@+id/keyfile_checkox"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignTop="@+id/keyfile_selection"
|
|
||||||
android:layout_marginTop="22dp"
|
|
||||||
android:contentDescription="@string/content_description_keyfile_checkbox"
|
|
||||||
android:focusable="false"
|
|
||||||
android:gravity="center_vertical" />
|
|
||||||
|
|
||||||
<com.kunzisoft.keepass.view.KeyFileSelectionView
|
|
||||||
android:id="@+id/keyfile_selection"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:layout_toRightOf="@+id/keyfile_checkox"
|
|
||||||
android:layout_toEndOf="@+id/keyfile_checkox"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:importantForAutofill="no" />
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Only to see previous view elevation -->
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/unlock_container"/>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|||||||
93
app/src/main/res/layout/view_credentials.xml
Normal file
93
app/src/main/res/layout/view_credentials.xml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/unlock_container"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:background="?android:attr/windowBackground"
|
||||||
|
app:layout_constraintWidth_percent="@dimen/content_percent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:targetApi="lollipop">
|
||||||
|
|
||||||
|
<!-- Password Input -->
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/password_input_container"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/password_checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/password_input_layout"
|
||||||
|
android:layout_marginTop="22dp"
|
||||||
|
android:contentDescription="@string/content_description_password_checkbox"
|
||||||
|
android:focusable="false"
|
||||||
|
android:gravity="center_vertical" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/password_input_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toRightOf="@+id/password_checkbox"
|
||||||
|
android:layout_toEndOf="@+id/password_checkbox"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
app:endIconMode="password_toggle"
|
||||||
|
app:endIconTint="?attr/colorAccent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:hint="@string/password"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:importantForAutofill="yes"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:autofillHints="password"
|
||||||
|
android:imeOptions="actionDone|flagNoPersonalizedLearning"
|
||||||
|
android:maxLines="1"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<!-- File Input -->
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/container_key_file"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/keyfile_checkox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/keyfile_selection"
|
||||||
|
android:layout_marginTop="22dp"
|
||||||
|
android:contentDescription="@string/content_description_keyfile_checkbox"
|
||||||
|
android:focusable="false"
|
||||||
|
android:gravity="center_vertical" />
|
||||||
|
|
||||||
|
<com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||||
|
android:id="@+id/keyfile_selection"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:layout_toRightOf="@+id/keyfile_checkox"
|
||||||
|
android:layout_toEndOf="@+id/keyfile_checkox"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:importantForAutofill="no" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user