Merge branch 'develop' into feature/OPEN_DOCUMENT

This commit is contained in:
J-Jamet
2019-10-05 11:45:54 +02:00
103 changed files with 1257 additions and 838 deletions

36
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,36 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**KeePass DX (please complete the following information):**
- Version: [e.g. 2.5.0.0beta23]
- Build: [e.g. Free]
**Android (please complete the following information):**
- Device: [e.g. GalaxyS8]
- Version [e.g. 8.1]
- Browser [e.g. Chrome]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,6 +1,7 @@
KeepassDX (2.5.0.0beta24)
* Add settings
* Add settings (Color, Security, Master Key)
* Show history of each entry
* Auto repair database for nodes with same UUID
* Fix settings
* Fix edit group
* Fix small bugs

View File

@@ -6,6 +6,7 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
ndkVersion "20.0.5594570"
defaultConfig {
applicationId "com.kunzisoft.keepass"
@@ -99,6 +100,8 @@ dependencies {
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Time
implementation 'joda-time:joda-time:2.9.9'
// Color
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
// Education
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
// Apache Commons Collections

View File

@@ -131,7 +131,8 @@
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
android:label="@string/keyboard_name">
android:label="@string/keyboard_name"
android:exported="true">
</activity>
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
android:label="@string/keyboard_setting_label">

View File

@@ -50,6 +50,7 @@ import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.EntryContentsView
import java.util.*
class EntryActivity : LockingHideActivity() {
@@ -86,7 +87,7 @@ class EntryActivity : LockingHideActivity() {
// Get Entry from UUID
try {
val keyEntry: PwNodeId<*> = intent.getParcelableExtra(KEY_ENTRY)
val keyEntry: PwNodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
mEntry = currentDatabase.getEntryById(keyEntry)
} catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key")

View File

@@ -44,6 +44,7 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView
import java.util.*
class EntryEditActivity : LockingHideActivity(),
IconPickerDialogFragment.IconPickerListener,
@@ -90,7 +91,7 @@ class EntryEditActivity : LockingHideActivity(),
mDatabase = Database.getInstance()
// Entry is retrieve, it's an entry to update
intent.getParcelableExtra<PwNodeId<*>>(KEY_ENTRY)?.let {
intent.getParcelableExtra<PwNodeId<UUID>>(KEY_ENTRY)?.let {
mIsNew = false
// Create an Entry copy to modify from the database entry
mEntry = mDatabase?.getEntryById(it)
@@ -176,7 +177,7 @@ class EntryEditActivity : LockingHideActivity(),
// Set info in view
entryEditContentsView?.apply {
title = newEntry.title
username = newEntry.username
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
url = newEntry.url
password = newEntry.password
notes = newEntry.notes

View File

@@ -311,7 +311,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
private val keyFileUri: Uri?) : ActionRunnable() {
override fun run() {
finishRun(true, null)
finishRun(true)
}
override fun onFinishRun(result: Result) {
@@ -353,7 +353,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
mDatabaseFileUri = data?.data
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
}
// else {
// TODO Show error

View File

@@ -28,7 +28,6 @@ import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.AttributeSet
import android.util.Log
import android.view.Menu
import android.view.MenuItem
@@ -78,6 +77,7 @@ class GroupActivity : LockingActivity(),
private var searchTitleView: View? = null
private var toolbarPaste: Toolbar? = null
private var iconView: ImageView? = null
private var numberChildrenView: TextView? = null
private var modeTitleView: TextView? = null
private var addNodeButtonView: AddNodeButtonView? = null
private var groupNameView: TextView? = null
@@ -110,7 +110,8 @@ class GroupActivity : LockingActivity(),
setContentView(layoutInflater.inflate(R.layout.activity_group, null))
// Initialize views
iconView = findViewById(R.id.icon)
iconView = findViewById(R.id.group_icon)
numberChildrenView = findViewById(R.id.group_numbers)
addNodeButtonView = findViewById(R.id.add_node_button)
toolbar = findViewById(R.id.toolbar)
searchTitleView = findViewById(R.id.search_title)
@@ -361,6 +362,16 @@ class GroupActivity : LockingActivity(),
}
}
// Assign number of children
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
// Show selection mode message if needed
if (mSelectionMode) {
modeTitleView?.visibility = View.VISIBLE

View File

@@ -207,7 +207,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (Database.getInstance().isRecycleBinAvailable
if (Database.getInstance().allowRecycleBin
&& Database.getInstance().isRecycleBinEnabled) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),

View File

@@ -44,6 +44,7 @@ import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.*
import androidx.biometric.BiometricManager
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.dialogs.FingerPrintExplanationDialog
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
@@ -61,6 +62,8 @@ import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException
import com.kunzisoft.keepass.database.search.SearchDbHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.MenuUtil
@@ -69,7 +72,6 @@ import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException
import java.lang.ref.WeakReference
class PasswordActivity : StylishActivity() {
@@ -87,6 +89,8 @@ class PasswordActivity : StylishActivity() {
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
private var prefs: SharedPreferences? = null
private var mRememberKeyFile: Boolean = false
@@ -101,8 +105,7 @@ class PasswordActivity : StylishActivity() {
prefs = PreferenceManager.getDefaultSharedPreferences(this)
mRememberKeyFile = prefs!!.getBoolean(getString(R.string.keyfile_key),
resources.getBoolean(R.bool.keyfile_default))
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
setContentView(R.layout.activity_password)
@@ -215,6 +218,7 @@ class PasswordActivity : StylishActivity() {
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
mDatabaseFileUri = databaseFileUri
mDatabaseKeyFileUri = keyFileUri
// Define title
databaseFileUri?.let {
@@ -236,11 +240,13 @@ class PasswordActivity : StylishActivity() {
newDefaultFileName = databaseFileUri ?: newDefaultFileName
}
newDefaultFileName?.let {
prefs?.edit()?.apply {
prefs?.edit()?.apply {
newDefaultFileName?.let {
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
apply()
} ?: kotlin.run {
remove(KEY_DEFAULT_DATABASE_PATH)
}
apply()
}
val backupManager = BackupManager(this@PasswordActivity)
@@ -384,14 +390,18 @@ class PasswordActivity : StylishActivity() {
keyFile: Uri?,
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
val keyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
loadDatabase(keyPassword, keyFileUri, cipherDatabaseEntity)
verifyKeyFileCheckbox(keyFile)
loadDatabase(mDatabaseFileUri, keyPassword, mDatabaseKeyFileUri, cipherDatabaseEntity)
}
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
val keyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
loadDatabase(password, keyFileUri)
verifyKeyFileCheckbox(keyFile)
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
}
private fun verifyKeyFileCheckbox(keyFile: Uri?) {
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
}
private fun removePassword() {
@@ -399,7 +409,10 @@ class PasswordActivity : StylishActivity() {
checkboxPasswordView?.isChecked = false
}
private fun loadDatabase(password: String?, keyFile: Uri?, cipherDatabaseEntity: CipherDatabaseEntity? = null) {
private fun loadDatabase(databaseFileUri: Uri?,
password: String?,
keyFileUri: Uri?,
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
runOnUiThread {
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
@@ -411,65 +424,119 @@ class PasswordActivity : StylishActivity() {
val database = Database.getInstance()
database.closeAndClear(applicationContext.filesDir)
mDatabaseFileUri?.let { databaseUri ->
// Show the progress dialog and load the database
ProgressDialogThread(this,
{ progressTaskUpdater ->
LoadDatabaseRunnable(
WeakReference(this@PasswordActivity),
database,
databaseUri,
password,
keyFile,
progressTaskUpdater,
AfterLoadingDatabase(database, password, cipherDatabaseEntity))
},
R.string.loading_database).start()
}
}
databaseFileUri?.let { databaseUri ->
/**
* Called after verify and try to opening the database
*/
private inner class AfterLoadingDatabase(val database: Database, val password: String?,
val cipherDatabaseEntity: CipherDatabaseEntity? = null)
: ActionRunnable() {
val onFinishLoadDatabase = object: ActionRunnable() {
override fun onFinishRun(result: Result) {
runOnUiThread {
// Recheck fingerprint if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
}
override fun onFinishRun(result: Result) {
runOnUiThread {
// Recheck fingerprint if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
}
if (result.isSuccess) {
// Remove the password in view in all cases
removePassword()
// Register the biometric
if (cipherDatabaseEntity != null) {
CipherDatabaseAction.getInstance(this@PasswordActivity)
.addOrUpdateCipherDatabase(cipherDatabaseEntity) {
checkAndLaunchGroupActivity(database, password)
if (result.isSuccess) {
// Save keyFile in app database
if (mRememberKeyFile) {
mDatabaseFileUri?.let { databaseUri ->
saveKeyFileData(databaseUri, mDatabaseKeyFileUri)
}
} else {
checkAndLaunchGroupActivity(database, password)
}
}
} else {
if (result.message != null && result.message!!.isNotEmpty()) {
Snackbar.make(activity_password_coordinator_layout, result.message!!, Snackbar.LENGTH_LONG).asError().show()
// Remove the password in view in all cases
removePassword()
// Register the biometric
if (cipherDatabaseEntity != null) {
CipherDatabaseAction.getInstance(this@PasswordActivity)
.addOrUpdateCipherDatabase(cipherDatabaseEntity) {
checkAndLaunchGroupActivity(database, password, keyFileUri)
}
} else {
checkAndLaunchGroupActivity(database, password, keyFileUri)
}
} else {
var resultError = ""
val resultException = result.exception
val resultMessage = result.message
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
if (resultException is LoadDatabaseDuplicateUuidException)
showLoadDatabaseDuplicateUuidMessage {
showProgressDialogAndLoadDatabase(database,
databaseUri,
password,
keyFileUri,
true,
this)
}
}
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError, resultException)
Snackbar.make(activity_password_coordinator_layout, resultError, Snackbar.LENGTH_LONG).asError().show()
}
}
}
}
// Show the progress dialog and load the database
showProgressDialogAndLoadDatabase(database,
databaseUri,
password,
keyFileUri,
false,
onFinishLoadDatabase)
}
}
private fun checkAndLaunchGroupActivity(database: Database, password: String?) {
if (database.validatePasswordEncoding(password)) {
private fun showProgressDialogAndLoadDatabase(database: Database,
databaseUri: Uri,
password: String?,
keyFile: Uri?,
fixDuplicateUUID: Boolean,
onFinishLoadDatabase: ActionRunnable) {
ProgressDialogThread(this,
{ progressTaskUpdater ->
LoadDatabaseRunnable(
database,
databaseUri,
password,
keyFile,
this@PasswordActivity.contentResolver,
this@PasswordActivity.filesDir,
SearchDbHelper(PreferencesUtil.omitBackup(this@PasswordActivity)),
fixDuplicateUUID,
progressTaskUpdater,
onFinishLoadDatabase)
},
R.string.loading_database).start()
}
private fun showLoadDatabaseDuplicateUuidMessage(loadDatabaseWithFix: (() -> Unit)? = null) {
DuplicateUuidDialog().apply {
positiveAction = loadDatabaseWithFix
}.show(supportFragmentManager, "duplicateUUIDDialog")
}
private fun saveKeyFileData(databaseUri: Uri, keyUri: Uri?) {
var keyFileUri = keyUri
if (!mRememberKeyFile) {
keyFileUri = null
}
FileDatabaseHistoryAction.getInstance(this).addOrUpdateDatabaseUri(databaseUri, keyFileUri)
}
private fun checkAndLaunchGroupActivity(database: Database, password: String?, keyFileUri: Uri?) {
if (database.validatePasswordEncoding(password, keyFileUri != null)) {
launchGroupActivity()
} else {
PasswordEncodingDialogFragment().apply {

View File

@@ -45,6 +45,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var rootView: View? = null
private var passwordCheckBox: CompoundButton? = null
private var passwordTextInputLayout: TextInputLayout? = null
private var passwordView: TextView? = null
private var passwordRepeatTextInputLayout: TextInputLayout? = null
private var passwordRepeatView: TextView? = null
@@ -96,6 +98,13 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
var allowNoMasterKey = false
arguments?.apply {
if (containsKey(ALLOW_NO_MASTER_KEY_ARG))
allowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
}
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
@@ -104,9 +113,10 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
.setTitle(R.string.assign_master_key)
// Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(R.string.cancel) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
passwordView = rootView?.findViewById(R.id.pass_password)
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
@@ -132,7 +142,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
var error = verifyPassword() || verifyFile()
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
error = true
showNoKeyConfirmationDialog()
if (allowNoMasterKey)
showNoKeyConfirmationDialog()
else {
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
}
}
if (!error) {
mListener?.onAssignKeyDialogPositiveClick(
@@ -193,6 +207,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
showEmptyPasswordConfirmationDialog()
}
}
return error
}
@@ -223,7 +238,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
this@AssignMasterKeyDialogFragment.dismiss()
}
}
.setNegativeButton(R.string.cancel) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
builder.create().show()
}
}
@@ -238,7 +253,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
keyFileCheckBox!!.isChecked, mKeyFile)
this@AssignMasterKeyDialogFragment.dismiss()
}
.setNegativeButton(R.string.cancel) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
builder.create().show()
}
}
@@ -255,4 +270,17 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
}
}
}
companion object {
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"
fun getInstance(allowNoMasterKey: Boolean): AssignMasterKeyDialogFragment {
val fragment = AssignMasterKeyDialogFragment()
val args = Bundle()
args.putBoolean(ALLOW_NO_MASTER_KEY_ARG, allowNoMasterKey)
fragment.arguments = args
return fragment
}
}
}

View File

@@ -36,7 +36,7 @@ class BrowserDialogFragment : DialogFragment() {
// Get the layout inflater
val root = activity.layoutInflater.inflate(R.layout.fragment_browser_install, null)
builder.setView(root)
.setNegativeButton(R.string.cancel) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
textDescription.text = getString(R.string.file_manager_install_description)

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class DuplicateUuidDialog : DialogFragment() {
var positiveAction: (() -> Unit)? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = androidx.appcompat.app.AlertDialog.Builder(activity).apply {
val message = getString(R.string.contains_duplicate_uuid) +
"\n\n" + getString(R.string.contains_duplicate_uuid_procedure)
setMessage(message)
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
positiveAction?.invoke()
dismiss()
}
setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onPause() {
super.onPause()
this.dismiss()
}
}

View File

@@ -114,7 +114,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
dismiss()
}
.setNegativeButton(R.string.cancel) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
val bundle = Bundle()
mListener?.cancelPassword(bundle)

View File

@@ -122,7 +122,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
val builder = AlertDialog.Builder(activity)
builder.setView(root)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(R.string.cancel) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ ->
editGroupListener?.cancelEditGroup(
editGroupDialogAction,
nameTextView?.text?.toString(),

View File

@@ -77,7 +77,7 @@ class IconPickerDialogFragment : DialogFragment() {
dismiss()
}
builder.setNegativeButton(R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog?.cancel() }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog?.cancel() }
return builder.create()
}

View File

@@ -35,7 +35,7 @@ class PasswordEncodingDialogFragment : DialogFragment() {
val builder = AlertDialog.Builder(activity)
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
builder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.cancel() }
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
return builder.create()
}

View File

@@ -83,7 +83,7 @@ class SortDialogFragment : DialogFragment() {
// Add action buttons
.setPositiveButton(android.R.string.ok
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) }
.setNegativeButton(R.string.cancel) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
// Check if is ascending or descending

View File

@@ -249,11 +249,18 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
biometricUnlockDatabaseHelper = null
}
// Only to fix multiple fingerprint menu #332
private var addBiometricMenuInProgress = false
fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) {
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
if ((biometricMode != Mode.UNAVAILABLE
&& biometricMode != Mode.NOT_CONFIGURED) && it)
menuInflater.inflate(R.menu.advanced_unlock, menu)
if (!addBiometricMenuInProgress) {
addBiometricMenuInProgress = true
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.NOT_CONFIGURED)
&& it) {
menuInflater.inflate(R.menu.advanced_unlock, menu)
addBiometricMenuInProgress = false
}
}
}
}

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.UriUtil
import java.io.IOException
@@ -63,7 +63,7 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
// To save the database
super.run()
finishRun(true)
} catch (e: InvalidKeyFileException) {
} catch (e: LoadDatabaseInvalidKeyFileException) {
erase(mBackupKey)
finishRun(false, e.message)
} catch (e: IOException) {

View File

@@ -19,118 +19,45 @@
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.content.ContentResolver
import android.net.Uri
import android.preference.PreferenceManager
import androidx.annotation.StringRes
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.search.SearchDbHelper
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.FileNotFoundException
import java.io.IOException
import java.lang.ref.WeakReference
import java.io.File
class LoadDatabaseRunnable(private val mWeakContext: WeakReference<Context>,
private val mDatabase: Database,
class LoadDatabaseRunnable(private val mDatabase: Database,
private val mUri: Uri,
private val mPass: String?,
private val mKey: Uri?,
private val contentResolver: ContentResolver,
private val cacheDirectory: File,
private val mSearchHelper: SearchDbHelper,
private val mFixDuplicateUUID: Boolean,
private val progressTaskUpdater: ProgressTaskUpdater?,
nestedAction: ActionRunnable)
: ActionRunnable(nestedAction, executeNestedActionIfResultFalse = true) {
private val mRememberKeyFile: Boolean
get() {
return mWeakContext.get()?.let {
PreferenceManager.getDefaultSharedPreferences(it)
.getBoolean(it.getString(R.string.keyfile_key),
it.resources.getBoolean(R.bool.keyfile_default))
} ?: true
}
override fun run() {
try {
mWeakContext.get()?.let {
mDatabase.loadData(it, mUri, mPass, mKey, progressTaskUpdater)
saveFileData(mUri, mKey)
finishRun(true)
} ?: finishRun(false, "Context null")
} catch (e: ArcFourException) {
catchError(e, R.string.error_arc4)
return
} catch (e: InvalidPasswordException) {
catchError(e, R.string.invalid_password)
return
} catch (e: ContentFileNotFoundException) {
catchError(e, R.string.file_not_found_content)
return
} catch (e: FileNotFoundException) {
catchError(e, R.string.file_not_found)
return
} catch (e: IOException) {
var messageId = R.string.error_load_database
e.message?.let {
if (it.contains("Hash failed with code"))
messageId = R.string.error_load_database_KDF_memory
}
catchError(e, messageId, true)
return
} catch (e: KeyFileEmptyException) {
catchError(e, R.string.keyfile_is_empty)
return
} catch (e: InvalidAlgorithmException) {
catchError(e, R.string.invalid_algorithm)
return
} catch (e: InvalidKeyFileException) {
catchError(e, R.string.keyfile_does_not_exist)
return
} catch (e: InvalidDBSignatureException) {
catchError(e, R.string.invalid_db_sig)
return
} catch (e: InvalidDBVersionException) {
catchError(e, R.string.unsupported_db_version)
return
} catch (e: InvalidDBException) {
catchError(e, R.string.error_invalid_db)
return
} catch (e: OutOfMemoryError) {
catchError(e, R.string.error_out_of_memory)
return
} catch (e: Exception) {
catchError(e, R.string.error_load_database, true)
return
mDatabase.loadData(mUri, mPass, mKey,
contentResolver,
cacheDirectory,
mSearchHelper,
mFixDuplicateUUID,
progressTaskUpdater)
finishRun(true)
}
}
private fun catchError(e: Throwable, @StringRes messageId: Int, addThrowableMessage: Boolean = false) {
var errorMessage = mWeakContext.get()?.getString(messageId)
Log.e(TAG, errorMessage, e)
if (addThrowableMessage)
errorMessage = errorMessage + " " + e.localizedMessage
finishRun(false, errorMessage)
}
private fun saveFileData(uri: Uri, key: Uri?) {
var keyFileUri = key
if (!mRememberKeyFile) {
keyFileUri = null
}
mWeakContext.get()?.let {
FileDatabaseHistoryAction.getInstance(it).addOrUpdateDatabaseUri(uri, keyFileUri)
catch (e: LoadDatabaseException) {
finishRun(false, e)
}
}
override fun onFinishRun(result: Result) {
if (!result.isSuccess) {
mDatabase.closeAndClear(mWeakContext.get()?.filesDir)
mDatabase.closeAndClear(cacheDirectory)
}
}
companion object {
private val TAG = LoadDatabaseRunnable::class.java.name
}
}

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.PwDbOutputException
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.tasks.ActionRunnable
import java.io.IOException
@@ -37,7 +37,7 @@ abstract class SaveDatabaseRunnable(protected var context: Context,
database.saveData(context.contentResolver)
} catch (e: IOException) {
finishRun(false, e.message)
} catch (e: PwDbOutputException) {
} catch (e: DatabaseOutputException) {
finishRun(false, e.message)
}
}

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.database.element
import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources
import android.database.Cursor
import android.net.Uri
@@ -39,7 +38,6 @@ import com.kunzisoft.keepass.database.file.save.PwDbV3Output
import com.kunzisoft.keepass.database.file.save.PwDbV4Output
import com.kunzisoft.keepass.database.search.SearchDbHelper
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder
@@ -56,7 +54,8 @@ class Database {
private var pwDatabaseV4: PwDatabaseV4? = null
private var mUri: Uri? = null
private var searchHelper: SearchDbHelper? = null
private var mSearchHelper: SearchDbHelper? = null
var isReadOnly = false
val drawFactory = IconDrawableFactory()
@@ -68,42 +67,120 @@ class Database {
return pwDatabaseV3?.iconFactory ?: pwDatabaseV4?.iconFactory ?: PwIconFactory()
}
val name: String
val allowName: Boolean
get() = pwDatabaseV4 != null
var name: String
get() {
return pwDatabaseV4?.name ?: ""
}
set(name) {
pwDatabaseV4?.name = name
pwDatabaseV4?.nameChanged = PwDate()
}
val description: String
val allowDescription: Boolean
get() = pwDatabaseV4 != null
var description: String
get() {
return pwDatabaseV4?.description ?: ""
}
set(description) {
pwDatabaseV4?.description = description
pwDatabaseV4?.descriptionChanged = PwDate()
}
val allowDefaultUsername: Boolean
get() = pwDatabaseV4 != null
// TODO get() = pwDatabaseV3 != null || pwDatabaseV4 != null
var defaultUsername: String
get() {
return pwDatabaseV4?.defaultUserName ?: ""
return pwDatabaseV4?.defaultUserName ?: "" // TODO pwDatabaseV3 default username
}
set(username) {
pwDatabaseV4?.defaultUserName = username
pwDatabaseV4?.defaultUserNameChanged = PwDate()
}
val allowCustomColor: Boolean
get() = pwDatabaseV4 != null
// TODO get() = pwDatabaseV3 != null || pwDatabaseV4 != null
// with format "#000000"
var customColor: String
get() {
return pwDatabaseV4?.color ?: "" // TODO pwDatabaseV3 color
}
set(value) {
// TODO Check color string
pwDatabaseV4?.color = value
}
val version: String
get() = pwDatabaseV3?.version ?: pwDatabaseV4?.version ?: "-"
val allowDataCompression: Boolean
get() = pwDatabaseV4 != null
val availableCompressionAlgorithms: List<PwCompressionAlgorithm>
get() = pwDatabaseV4?.availableCompressionAlgorithms ?: ArrayList()
val compressionAlgorithm: PwCompressionAlgorithm?
var compressionAlgorithm: PwCompressionAlgorithm?
get() = pwDatabaseV4?.compressionAlgorithm
set(value) {
value?.let {
pwDatabaseV4?.compressionAlgorithm = it
}
}
val allowNoMasterKey: Boolean
get() = pwDatabaseV4 != null
val allowEncryptionAlgorithmModification: Boolean
get() = availableEncryptionAlgorithms.size > 1
fun getEncryptionAlgorithmName(resources: Resources): String {
return pwDatabaseV3?.encryptionAlgorithm?.getName(resources)
?: pwDatabaseV4?.encryptionAlgorithm?.getName(resources)
?: ""
}
val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
get() = pwDatabaseV3?.availableEncryptionAlgorithms ?: pwDatabaseV4?.availableEncryptionAlgorithms ?: ArrayList()
val encryptionAlgorithm: PwEncryptionAlgorithm?
var encryptionAlgorithm: PwEncryptionAlgorithm?
get() = pwDatabaseV3?.encryptionAlgorithm ?: pwDatabaseV4?.encryptionAlgorithm
set(algorithm) {
algorithm?.let {
pwDatabaseV4?.encryptionAlgorithm = algorithm
pwDatabaseV4?.setDataEngine(algorithm.cipherEngine)
pwDatabaseV4?.dataCipher = algorithm.dataCipher
}
}
val availableKdfEngines: List<KdfEngine>
get() = pwDatabaseV3?.kdfAvailableList ?: pwDatabaseV4?.kdfAvailableList ?: ArrayList()
val kdfEngine: KdfEngine?
val allowKdfModification: Boolean
get() = availableKdfEngines.size > 1
var kdfEngine: KdfEngine?
get() = pwDatabaseV3?.kdfEngine ?: pwDatabaseV4?.kdfEngine
set(kdfEngine) {
kdfEngine?.let {
if (pwDatabaseV4?.kdfParameters?.uuid != kdfEngine.defaultParameters.uuid)
pwDatabaseV4?.kdfParameters = kdfEngine.defaultParameters
numberKeyEncryptionRounds = kdfEngine.defaultKeyRounds
memoryUsage = kdfEngine.defaultMemoryUsage
parallelism = kdfEngine.defaultParallelism
}
}
fun getKeyDerivationName(resources: Resources): String {
return kdfEngine?.getName(resources) ?: ""
}
var numberKeyEncryptionRounds: Long
get() = pwDatabaseV3?.numberKeyEncryptionRounds ?: pwDatabaseV4?.numberKeyEncryptionRounds ?: 0
@@ -168,7 +245,7 @@ class Database {
* Determine if RecycleBin is available or not for this version of database
* @return true if RecycleBin available
*/
val isRecycleBinAvailable: Boolean
val allowRecycleBin: Boolean
get() = pwDatabaseV4 != null
val isRecycleBinEnabled: Boolean
@@ -209,78 +286,95 @@ class Database {
this.mUri = databaseUri
}
@Throws(IOException::class, InvalidDBException::class)
fun loadData(ctx: Context, uri: Uri, password: String?, keyfile: Uri?, progressTaskUpdater: ProgressTaskUpdater?) {
@Throws(LoadDatabaseException::class)
fun loadData(uri: Uri, password: String?, keyfile: Uri?,
contentResolver: ContentResolver,
cacheDirectory: File,
searchHelper: SearchDbHelper,
fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
mUri = uri
isReadOnly = false
if (uri.scheme == "file") {
val file = File(uri.path!!)
isReadOnly = !file.canWrite()
}
// Pass Uris as InputStreams
val inputStream: InputStream?
try {
inputStream = UriUtil.getUriInputStream(ctx.contentResolver, uri)
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw ContentFileNotFoundException.getInstance(uri)
}
// Pass KeyFile Uri as InputStreams
var keyFileInputStream: InputStream? = null
keyfile?.let {
mUri = uri
isReadOnly = false
if (uri.scheme == "file") {
val file = File(uri.path!!)
isReadOnly = !file.canWrite()
}
// Pass Uris as InputStreams
val inputStream: InputStream?
try {
keyFileInputStream = UriUtil.getUriInputStream(ctx.contentResolver, keyfile)
inputStream = UriUtil.getUriInputStream(contentResolver, uri)
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw ContentFileNotFoundException.getInstance(keyfile)
throw LoadDatabaseFileNotFoundException()
}
}
// Load Data
// Pass KeyFile Uri as InputStreams
var keyFileInputStream: InputStream? = null
keyfile?.let {
try {
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw LoadDatabaseFileNotFoundException()
}
}
val bufferedInputStream = BufferedInputStream(inputStream)
if (!bufferedInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
// Load Data
// We'll end up reading 8 bytes to identify the header. Might as well use two extra.
bufferedInputStream.mark(10)
val bufferedInputStream = BufferedInputStream(inputStream)
if (!bufferedInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
// Get the file directory to save the attachments
val sig1 = LEDataInputStream.readInt(bufferedInputStream)
val sig2 = LEDataInputStream.readInt(bufferedInputStream)
// We'll end up reading 8 bytes to identify the header. Might as well use two extra.
bufferedInputStream.mark(10)
// Return to the start
bufferedInputStream.reset()
// Get the file directory to save the attachments
val sig1 = LEDataInputStream.readInt(bufferedInputStream)
val sig2 = LEDataInputStream.readInt(bufferedInputStream)
when {
// Header of database V3
PwDbHeaderV3.matchesHeader(sig1, sig2) -> setDatabaseV3(ImporterV3()
.openDatabase(bufferedInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
// Return to the start
bufferedInputStream.reset()
// Header of database V4
PwDbHeaderV4.matchesHeader(sig1, sig2) -> setDatabaseV4(ImporterV4(ctx.filesDir)
.openDatabase(bufferedInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
when {
// Header of database V3
PwDbHeaderV3.matchesHeader(sig1, sig2) -> setDatabaseV3(ImporterV3()
.openDatabase(bufferedInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
// Header not recognized
else -> throw InvalidDBSignatureException()
}
// Header of database V4
PwDbHeaderV4.matchesHeader(sig1, sig2) -> setDatabaseV4(ImporterV4(
cacheDirectory,
fixDuplicateUUID)
.openDatabase(bufferedInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
try {
searchHelper = SearchDbHelper(PreferencesUtil.omitBackup(ctx))
// Header not recognized
else -> throw LoadDatabaseSignatureException()
}
this.mSearchHelper = searchHelper
loaded = true
} catch (e: LoadDatabaseException) {
throw e
} catch (e: IOException) {
if (e.message?.contains("Hash failed with code") == true)
throw LoadDatabaseKDFMemoryException(e)
else
throw LoadDatabaseIOException(e)
} catch (e: OutOfMemoryError) {
throw LoadDatabaseNoMemoryException(e)
} catch (e: Exception) {
Log.e(TAG, "Load can't be performed with this Database version", e)
loaded = false
throw LoadDatabaseException(e)
}
}
@@ -292,7 +386,7 @@ class Database {
@JvmOverloads
fun search(str: String, max: Int = Integer.MAX_VALUE): GroupVersioned? {
return searchHelper?.search(this, str, max)
return mSearchHelper?.search(this, str, max)
}
fun searchEntries(query: String): Cursor? {
@@ -343,14 +437,14 @@ class Database {
return entry
}
@Throws(IOException::class, PwDbOutputException::class)
@Throws(IOException::class, DatabaseOutputException::class)
fun saveData(contentResolver: ContentResolver) {
mUri?.let {
saveData(contentResolver, it)
}
}
@Throws(IOException::class, PwDbOutputException::class)
@Throws(IOException::class, DatabaseOutputException::class)
private fun saveData(contentResolver: ContentResolver, uri: Uri) {
val errorMessage = "Failed to store database."
@@ -419,72 +513,13 @@ class Database {
loaded = false
}
fun getVersion(): String {
return pwDatabaseV3?.version ?: pwDatabaseV4?.version ?: "unknown"
}
fun containsName(): Boolean {
pwDatabaseV4?.let { return true }
return false
}
fun assignName(name: String) {
pwDatabaseV4?.name = name
pwDatabaseV4?.nameChanged = PwDate()
}
fun containsDescription(): Boolean {
pwDatabaseV4?.let { return true }
return false
}
fun assignDescription(description: String) {
pwDatabaseV4?.description = description
pwDatabaseV4?.descriptionChanged = PwDate()
}
fun assignCompressionAlgorithm(algorithm: PwCompressionAlgorithm) {
pwDatabaseV4?.compressionAlgorithm = algorithm
// TODO Compression
}
fun allowEncryptionAlgorithmModification(): Boolean {
return availableEncryptionAlgorithms.size > 1
}
fun assignEncryptionAlgorithm(algorithm: PwEncryptionAlgorithm) {
pwDatabaseV4?.encryptionAlgorithm = algorithm
pwDatabaseV4?.setDataEngine(algorithm.cipherEngine)
pwDatabaseV4?.dataCipher = algorithm.dataCipher
}
fun getEncryptionAlgorithmName(resources: Resources): String {
return pwDatabaseV3?.encryptionAlgorithm?.getName(resources) ?: pwDatabaseV4?.encryptionAlgorithm?.getName(resources) ?: ""
}
fun allowKdfModification(): Boolean {
return availableKdfEngines.size > 1
}
fun assignKdfEngine(kdfEngine: KdfEngine) {
if (pwDatabaseV4?.kdfParameters?.uuid != kdfEngine.defaultParameters.uuid)
pwDatabaseV4?.kdfParameters = kdfEngine.defaultParameters
numberKeyEncryptionRounds = kdfEngine.defaultKeyRounds
memoryUsage = kdfEngine.defaultMemoryUsage
parallelism = kdfEngine.defaultParallelism
}
fun getKeyDerivationName(resources: Resources): String {
return kdfEngine?.getName(resources) ?: ""
}
fun validatePasswordEncoding(key: String?): Boolean {
return pwDatabaseV3?.validatePasswordEncoding(key)
?: pwDatabaseV4?.validatePasswordEncoding(key)
fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
return pwDatabaseV3?.validatePasswordEncoding(password, containsKeyFile)
?: pwDatabaseV4?.validatePasswordEncoding(password, containsKeyFile)
?: false
}
@Throws(InvalidKeyFileException::class, IOException::class)
@Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class)
fun retrieveMasterKey(key: String?, keyInputStream: InputStream?) {
pwDatabaseV3?.retrieveMasterKey(key, keyInputStream)
pwDatabaseV4?.retrieveMasterKey(key, keyInputStream)
@@ -524,7 +559,7 @@ class Database {
return null
}
fun getEntryById(id: PwNodeId<*>): EntryVersioned? {
fun getEntryById(id: PwNodeId<UUID>): EntryVersioned? {
pwDatabaseV3?.getEntryById(id)?.let {
return EntryVersioned(it)
}
@@ -535,12 +570,14 @@ class Database {
}
fun getGroupById(id: PwNodeId<*>): GroupVersioned? {
pwDatabaseV3?.getGroupById(id)?.let {
return GroupVersioned(it)
}
pwDatabaseV4?.getGroupById(id)?.let {
return GroupVersioned(it)
}
if (id is PwNodeIdInt)
pwDatabaseV3?.getGroupById(id)?.let {
return GroupVersioned(it)
}
else if (id is PwNodeIdUUID)
pwDatabaseV4?.getGroupById(id)?.let {
return GroupVersioned(it)
}
return null
}

View File

@@ -19,10 +19,10 @@
*/
package com.kunzisoft.keepass.database.element
import android.util.Log
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
import com.kunzisoft.keepass.database.exception.KeyFileEmptyException
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException
import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException
import com.kunzisoft.keepass.database.exception.LoadDatabaseKeyFileEmptyException
import com.kunzisoft.keepass.utils.MemoryUtil
import java.io.ByteArrayInputStream
@@ -35,7 +35,11 @@ import java.security.NoSuchAlgorithmException
import java.util.LinkedHashMap
import java.util.UUID
abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Group, Entry>> {
abstract class PwDatabase<
GroupId,
Group : PwGroup<GroupId, Group, Entry>,
Entry : PwEntry<Group, Entry>
> {
// Algorithm used to encrypt the database
protected var algorithm: PwEncryptionAlgorithm? = null
@@ -51,8 +55,10 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
var iconFactory = PwIconFactory()
protected set
private var groupIndexes = LinkedHashMap<PwNodeId<*>, Group>()
private var entryIndexes = LinkedHashMap<PwNodeId<*>, Entry>()
var changeDuplicateId = false
private var groupIndexes = LinkedHashMap<PwNodeId<GroupId>, Group>()
private var entryIndexes = LinkedHashMap<PwNodeId<UUID>, Entry>()
abstract val version: String
@@ -72,15 +78,15 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
var rootGroup: Group? = null
@Throws(InvalidKeyFileException::class, IOException::class)
@Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class)
protected abstract fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray
@Throws(InvalidKeyFileException::class, IOException::class)
@Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class)
fun retrieveMasterKey(key: String?, keyInputStream: InputStream?) {
masterKey = getMasterKey(key, keyInputStream)
}
@Throws(InvalidKeyFileException::class, IOException::class)
@Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class)
protected fun getCompositeKey(key: String, keyInputStream: InputStream): ByteArray {
val fileKey = getFileKey(keyInputStream)
val passwordKey = getPasswordKey(key)
@@ -120,7 +126,7 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
return messageDigest.digest()
}
@Throws(InvalidKeyFileException::class, IOException::class)
@Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class)
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyByteArrayOutputStream = ByteArrayOutputStream()
@@ -134,7 +140,7 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
}
when (keyData.size.toLong()) {
0L -> throw KeyFileEmptyException()
0L -> throw LoadDatabaseKeyFileEmptyException()
32L -> return keyData
64L -> try {
return hexStringToByteArray(String(keyData))
@@ -161,15 +167,18 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
protected abstract fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray?
open fun validatePasswordEncoding(key: String?): Boolean {
if (key == null)
open fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
if (password == null && !containsKeyFile)
return false
if (password == null)
return true
val encoding = passwordEncoding
val bKey: ByteArray
try {
bKey = key.toByteArray(charset(encoding))
bKey = password.toByteArray(charset(encoding))
} catch (e: UnsupportedEncodingException) {
return false
}
@@ -180,7 +189,7 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
} catch (e: UnsupportedEncodingException) {
return false
}
return key == reEncoded
return password == reEncoded
}
/*
@@ -189,9 +198,9 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
* -------------------------------------
*/
abstract fun newGroupId(): PwNodeId<*>
abstract fun newGroupId(): PwNodeId<GroupId>
abstract fun newEntryId(): PwNodeId<*>
abstract fun newEntryId(): PwNodeId<UUID>
abstract fun createGroup(): Group
@@ -216,7 +225,7 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
* ID number to check for
* @return True if the ID is used, false otherwise
*/
fun isGroupIdUsed(id: PwNodeId<*>): Boolean {
fun isGroupIdUsed(id: PwNodeId<GroupId>): Boolean {
return groupIndexes.containsKey(id)
}
@@ -231,14 +240,21 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
}
}
fun getGroupById(id: PwNodeId<*>): Group? {
fun getGroupById(id: PwNodeId<GroupId>): Group? {
return this.groupIndexes[id]
}
fun addGroupIndex(group: Group) {
val groupId = group.nodeId
if (groupIndexes.containsKey(groupId)) {
Log.e(TAG, "Error, a group with the same UUID $groupId already exists")
if (changeDuplicateId) {
val newGroupId = newGroupId()
group.nodeId = newGroupId
group.parent?.addChildGroup(group)
this.groupIndexes[newGroupId] = group
} else {
throw LoadDatabaseDuplicateUuidException(Type.GROUP, groupId)
}
} else {
this.groupIndexes[groupId] = group
}
@@ -258,7 +274,7 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
}
}
fun isEntryIdUsed(id: PwNodeId<*>): Boolean {
fun isEntryIdUsed(id: PwNodeId<UUID>): Boolean {
return entryIndexes.containsKey(id)
}
@@ -266,15 +282,21 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
return entryIndexes.values
}
fun getEntryById(id: PwNodeId<*>): Entry? {
fun getEntryById(id: PwNodeId<UUID>): Entry? {
return this.entryIndexes[id]
}
fun addEntryIndex(entry: Entry) {
val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) {
// TODO History
Log.e(TAG, "Error, a group with the same UUID $entryId already exists, change the UUID")
if (changeDuplicateId) {
val newEntryId = newEntryId()
entry.nodeId = newEntryId
entry.parent?.addChildEntry(entry)
this.entryIndexes[newEntryId] = entry
} else {
throw LoadDatabaseDuplicateUuidException(Type.ENTRY, entryId)
}
} else {
this.entryIndexes[entryId] = entry
}

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.IOException
import java.io.InputStream
@@ -30,7 +30,7 @@ import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
class PwDatabaseV3 : PwDatabase<PwGroupV3, PwEntryV3>() {
class PwDatabaseV3 : PwDatabase<Int, PwGroupV3, PwEntryV3>() {
private var numKeyEncRounds: Int = 0
@@ -112,7 +112,7 @@ class PwDatabaseV3 : PwDatabase<PwGroupV3, PwEntryV3>() {
return newId
}
@Throws(InvalidKeyFileException::class, IOException::class)
@Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class)
override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {
return if (key != null && keyInputStream != null) {

View File

@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.*
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node
@@ -40,7 +40,7 @@ import java.util.*
import javax.xml.parsers.DocumentBuilderFactory
class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
class PwDatabaseV4 : PwDatabase<UUID, PwGroupV4, PwEntryV4> {
var hmacKey: ByteArray? = null
private set
@@ -236,7 +236,7 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
return getCustomData().isNotEmpty()
}
@Throws(InvalidKeyFileException::class, IOException::class)
@Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class)
public override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {
var masterKey = byteArrayOf()
@@ -461,10 +461,10 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
return publicCustomData.size() > 0
}
override fun validatePasswordEncoding(key: String?): Boolean {
if (key == null)
override fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
if (password == null)
return true
return super.validatePasswordEncoding(key)
return super.validatePasswordEncoding(password, containsKeyFile)
}
override fun clearCache() {

View File

@@ -1,27 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
class ArcFourException : InvalidDBException() {
companion object {
private const val serialVersionUID = 2103983626687861237L
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
import android.net.Uri
import java.io.FileNotFoundException
class ContentFileNotFoundException : FileNotFoundException() {
companion object {
fun getInstance(uri: Uri?): FileNotFoundException {
if (uri == null) {
return FileNotFoundException()
}
val scheme = uri.scheme
return if (scheme != null
&& scheme.isNotEmpty()
&& scheme.equals("content", ignoreCase = true)) {
ContentFileNotFoundException()
} else FileNotFoundException()
}
}
}

View File

@@ -19,14 +19,10 @@
*/
package com.kunzisoft.keepass.database.exception
class PwDbOutputException : Exception {
class DatabaseOutputException : Exception {
constructor(string: String) : super(string)
constructor(string: String, e: Exception) : super(string, e)
constructor(e: Exception) : super(e)
companion object {
private const val serialVersionUID = 3321212743159473368L
}
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
class InvalidAlgorithmException : InvalidDBException() {
companion object {
private const val serialVersionUID = 3062682891863487208L
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
open class InvalidDBException : Exception {
constructor(str: String) : super(str)
constructor() : super()
companion object {
private const val serialVersionUID = 5191964825154190923L
}
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
class InvalidDBSignatureException : InvalidDBException() {
companion object {
private const val serialVersionUID = -5358923878743513758L
}
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
class InvalidDBVersionException : InvalidDBException() {
companion object {
private const val serialVersionUID = -4260650987856400586L
}
}

View File

@@ -1,25 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/package com.kunzisoft.keepass.database.exception
open class InvalidKeyFileException : InvalidDBException() {
companion object {
private const val serialVersionUID = 5540694419562294464L
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
class InvalidPasswordException : InvalidDBException() {
companion object {
private const val serialVersionUID = -8729476180242058319L
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
class KeyFileEmptyException : InvalidKeyFileException() {
companion object {
private const val serialVersionUID = -1630780661204212325L
}
}

View File

@@ -0,0 +1,75 @@
package com.kunzisoft.keepass.database.exception
import android.content.res.Resources
import androidx.annotation.StringRes
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.database.element.Type
import java.io.IOException
class LoadDatabaseArcFourException :
LoadDatabaseException(R.string.error_arc4)
class LoadDatabaseFileNotFoundException :
LoadDatabaseException(R.string.file_not_found_content)
class LoadDatabaseInvalidAlgorithmException :
LoadDatabaseException(R.string.invalid_algorithm)
class LoadDatabaseDuplicateUuidException(type: Type, uuid: PwNodeId<*>):
LoadDatabaseException(R.string.invalid_db_same_uuid, type.name, uuid.toString())
class LoadDatabaseIOException(exception: IOException) :
LoadDatabaseException(exception, R.string.error_load_database)
class LoadDatabaseKDFMemoryException(exception: IOException) :
LoadDatabaseException(exception, R.string.error_load_database_KDF_memory)
class LoadDatabaseSignatureException :
LoadDatabaseException(R.string.invalid_db_sig)
class LoadDatabaseVersionException :
LoadDatabaseException(R.string.unsupported_db_version)
open class LoadDatabaseInvalidKeyFileException :
LoadDatabaseException(R.string.keyfile_does_not_exist)
class LoadDatabaseInvalidPasswordException :
LoadDatabaseException(R.string.invalid_password)
class LoadDatabaseKeyFileEmptyException :
LoadDatabaseException(R.string.keyfile_is_empty)
class LoadDatabaseNoMemoryException(exception: OutOfMemoryError) :
LoadDatabaseException(exception, R.string.error_out_of_memory)
open class LoadDatabaseException : Exception {
@StringRes
var errorId: Int = R.string.error_load_database
var parameters: (Array<out String>)? = null
constructor(errorMessageId: Int) : super() {
errorId = errorMessageId
}
constructor(errorMessageId: Int, vararg params: String) : super() {
errorId = errorMessageId
parameters = params
}
constructor(throwable: Throwable, errorMessageId: Int? = null) : super(throwable) {
errorMessageId?.let {
errorId = it
}
}
constructor() : super()
fun getLocalizedMessage(resources: Resources): String {
parameters?.let {
return resources.getString(errorId, *it)
} ?: return resources.getString(errorId)
}
}

View File

@@ -19,9 +19,4 @@
*/
package com.kunzisoft.keepass.database.exception
class SamsungClipboardException(e: Exception) : Exception(e) {
companion object {
private const val serialVersionUID = -3168837280393843509L
}
}
class SamsungClipboardException(e: Exception) : Exception(e)

View File

@@ -2,8 +2,4 @@ package com.kunzisoft.keepass.database.exception
import java.io.IOException
class UnknownKDF : IOException(message) {
companion object {
private const val message = "Unknown key derivation function"
}
}
class UnknownKDF : IOException("Unknown key derivation function")

View File

@@ -25,7 +25,7 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.NodeHandler
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.InvalidDBVersionException
import com.kunzisoft.keepass.database.exception.LoadDatabaseVersionException
import com.kunzisoft.keepass.stream.CopyInputStream
import com.kunzisoft.keepass.stream.HmacBlockStream
import com.kunzisoft.keepass.stream.LEDataInputStream
@@ -130,9 +130,9 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
/** Assumes the input stream is at the beginning of the .kdbx file
* @param inputStream
* @throws IOException
* @throws InvalidDBVersionException
* @throws LoadDatabaseVersionException
*/
@Throws(IOException::class, InvalidDBVersionException::class)
@Throws(IOException::class, LoadDatabaseVersionException::class)
fun loadFromFile(inputStream: InputStream): HeaderAndHash {
val messageDigest: MessageDigest
try {
@@ -150,12 +150,12 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
val sig2 = littleEndianDataInputStream.readInt()
if (!matchesHeader(sig1, sig2)) {
throw InvalidDBVersionException()
throw LoadDatabaseVersionException()
}
version = littleEndianDataInputStream.readUInt() // Erase previous value
if (!validVersion(version)) {
throw InvalidDBVersionException()
throw LoadDatabaseVersionException()
}
var done = false

View File

@@ -20,13 +20,13 @@
package com.kunzisoft.keepass.database.file.load
import com.kunzisoft.keepass.database.element.PwDatabase
import com.kunzisoft.keepass.database.exception.InvalidDBException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.IOException
import java.io.InputStream
abstract class Importer<PwDb : PwDatabase<*, *>> {
abstract class Importer<PwDb : PwDatabase<*, *, *>> {
/**
* Load a versioned database file, return contents in a new PwDatabase.
@@ -36,9 +36,9 @@ abstract class Importer<PwDb : PwDatabase<*, *>> {
* @return new PwDatabase container.
*
* @throws IOException on any file error.
* @throws InvalidDBException on database error.
* @throws LoadDatabaseException on database error.
*/
@Throws(IOException::class, InvalidDBException::class)
@Throws(IOException::class, LoadDatabaseException::class)
abstract fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,

View File

@@ -73,7 +73,7 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
private lateinit var mDatabaseToOpen: PwDatabaseV3
@Throws(IOException::class, InvalidDBException::class)
@Throws(IOException::class, LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
@@ -92,11 +92,11 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
hdr.loadFromFile(filebuf, 0)
if (hdr.signature1 != PwDbHeader.PWM_DBSIG_1 || hdr.signature2 != PwDbHeaderV3.DBSIG_2) {
throw InvalidDBSignatureException()
throw LoadDatabaseSignatureException()
}
if (!hdr.matchesVersion()) {
throw InvalidDBVersionException()
throw LoadDatabaseVersionException()
}
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
@@ -109,7 +109,7 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
} else if (hdr.flags and PwDbHeaderV3.FLAG_TWOFISH != 0) {
mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.Twofish
} else {
throw InvalidAlgorithmException()
throw LoadDatabaseInvalidAlgorithmException()
}
mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong()
@@ -152,7 +152,7 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
} catch (e1: IllegalBlockSizeException) {
throw IOException("Invalid block size")
} catch (e1: BadPaddingException) {
throw InvalidPasswordException()
throw LoadDatabaseInvalidPasswordException()
}
val md: MessageDigest
@@ -171,7 +171,7 @@ class ImporterV3 : Importer<PwDatabaseV3>() {
if (!Arrays.equals(hash, hdr.contentsHash)) {
Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)")
throw InvalidPasswordException()
throw LoadDatabaseInvalidPasswordException()
}
// New manual root because V3 contains multiple root groups (here available with getRootGroups())

View File

@@ -26,9 +26,9 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.ArcFourException
import com.kunzisoft.keepass.database.exception.InvalidDBException
import com.kunzisoft.keepass.database.exception.InvalidPasswordException
import com.kunzisoft.keepass.database.exception.LoadDatabaseArcFourException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidPasswordException
import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.security.ProtectedString
@@ -56,7 +56,8 @@ import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import kotlin.math.min
class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
class ImporterV4(private val streamDir: File,
private val fixDuplicateUUID: Boolean = false) : Importer<PwDatabaseV4>() {
private var randomStream: StreamCipher? = null
private lateinit var mDatabase: PwDatabaseV4
@@ -89,7 +90,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
private var entryCustomDataKey: String? = null
private var entryCustomDataValue: String? = null
@Throws(IOException::class, InvalidDBException::class)
@Throws(IOException::class, LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
@@ -99,6 +100,9 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
mDatabase = PwDatabaseV4()
mDatabase.changeDuplicateId = fixDuplicateUUID
val header = PwDbHeaderV4(mDatabase)
val headerAndHash = header.loadFromFile(databaseInputStream)
@@ -138,14 +142,14 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
try {
storedStartBytes = dataDecrypted.readBytes(32)
if (storedStartBytes == null || storedStartBytes.size != 32) {
throw InvalidPasswordException()
throw LoadDatabaseInvalidPasswordException()
}
} catch (e: IOException) {
throw InvalidPasswordException()
throw LoadDatabaseInvalidPasswordException()
}
if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) {
throw InvalidPasswordException()
throw LoadDatabaseInvalidPasswordException()
}
isPlain = HashedBlockInputStream(dataDecrypted)
@@ -153,18 +157,18 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
val isData = LEDataInputStream(databaseInputStream)
val storedHash = isData.readBytes(32)
if (!Arrays.equals(storedHash, hashOfHeader)) {
throw InvalidDBException()
throw LoadDatabaseException()
}
val hmacKey = mDatabase.hmacKey ?: throw InvalidDBException()
val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException()
val headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey)
val storedHmac = isData.readBytes(32)
if (storedHmac == null || storedHmac.size != 32) {
throw InvalidDBException()
throw LoadDatabaseException()
}
// Mac doesn't match
if (!Arrays.equals(headerHmac, storedHmac)) {
throw InvalidDBException()
throw LoadDatabaseException()
}
val hmIs = HmacBlockInputStream(isData, true, hmacKey)
@@ -185,7 +189,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
if (randomStream == null) {
throw ArcFourException()
throw LoadDatabaseArcFourException()
}
readXmlStreamed(isXml)
@@ -270,7 +274,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
Binaries
}
@Throws(IOException::class, InvalidDBException::class)
@Throws(IOException::class, LoadDatabaseException::class)
private fun readXmlStreamed(readerStream: InputStream) {
try {
readDocumentStreamed(createPullParser(readerStream))
@@ -281,7 +285,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
}
@Throws(XmlPullParserException::class, IOException::class, InvalidDBException::class)
@Throws(XmlPullParserException::class, IOException::class, LoadDatabaseException::class)
private fun readDocumentStreamed(xpp: XmlPullParser) {
ctxGroups.clear()
@@ -312,7 +316,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
if (ctxGroups.size != 0) throw IOException("Malformed")
}
@Throws(XmlPullParserException::class, IOException::class, InvalidDBException::class)
@Throws(XmlPullParserException::class, IOException::class, LoadDatabaseException::class)
private fun readXmlElement(ctx: KdbContext, xpp: XmlPullParser): KdbContext {
val name = xpp.name
when (ctx) {
@@ -336,7 +340,7 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
if (encodedHash.isNotEmpty() && hashOfHeader != null) {
val hash = Base64Coder.decode(encodedHash)
if (!Arrays.equals(hash, hashOfHeader)) {
throw InvalidDBException()
throw LoadDatabaseException()
}
}
} else if (name.equals(PwDatabaseV4XML.ElemSettingsChanged, ignoreCase = true)) {
@@ -354,7 +358,6 @@ class ImporterV4(private val streamDir: File) : Importer<PwDatabaseV4>() {
} else if (name.equals(PwDatabaseV4XML.ElemDbDefaultUserChanged, ignoreCase = true)) {
mDatabase.defaultUserNameChanged = readPwTime(xpp)
} else if (name.equals(PwDatabaseV4XML.ElemDbColor, ignoreCase = true)) {
// TODO: Add support to interpret the color if we want to allow changing the database color
mDatabase.color = readString(xpp)
} else if (name.equals(PwDatabaseV4XML.ElemDbMntncHistoryDays, ignoreCase = true)) {
mDatabase.maintenanceHistoryDays = readUInt(xpp, DEFAULT_HISTORY_DAYS)

View File

@@ -24,7 +24,7 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.element.PwDatabaseV4
import com.kunzisoft.keepass.database.file.PwDbHeader
import com.kunzisoft.keepass.database.file.PwDbHeaderV4
import com.kunzisoft.keepass.database.exception.PwDbOutputException
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.stream.HmacBlockStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.MacOutputStream
@@ -41,7 +41,7 @@ import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class PwDbHeaderOutputV4 @Throws(PwDbOutputException::class)
class PwDbHeaderOutputV4 @Throws(DatabaseOutputException::class)
constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: OutputStream) : PwDbHeaderOutput() {
private val los: LEDataOutputStream
private val mos: MacOutputStream
@@ -54,13 +54,13 @@ constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os:
try {
md = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw PwDbOutputException("SHA-256 not implemented here.")
throw DatabaseOutputException("SHA-256 not implemented here.")
}
try {
db.makeFinalKey(header.masterSeed)
} catch (e: IOException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
}
val hmac: Mac
@@ -69,9 +69,9 @@ constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os:
val signingKey = SecretKeySpec(HmacBlockStream.GetHmacKey64(db.hmacKey, Types.ULONG_MAX_VALUE), "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
} catch (e: InvalidKeyException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
}
dos = DigestOutputStream(os, md)

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.file.save
import com.kunzisoft.keepass.database.file.PwDbHeader
import com.kunzisoft.keepass.database.exception.PwDbOutputException
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import java.io.OutputStream
import java.security.NoSuchAlgorithmException
@@ -28,13 +28,13 @@ import java.security.SecureRandom
abstract class PwDbOutput<Header : PwDbHeader> protected constructor(protected var mOS: OutputStream) {
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
protected open fun setIVs(header: Header): SecureRandom {
val random: SecureRandom
try {
random = SecureRandom.getInstance("SHA1PRNG")
} catch (e: NoSuchAlgorithmException) {
throw PwDbOutputException("Does not support secure random number generation.")
throw DatabaseOutputException("Does not support secure random number generation.")
}
random.nextBytes(header.encryptionIV)
@@ -43,10 +43,10 @@ abstract class PwDbOutput<Header : PwDbHeader> protected constructor(protected v
return random
}
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
abstract fun output()
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
abstract fun outputHeader(outputStream: OutputStream): Header
}

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.file.save
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.PwDbOutputException
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.PwDbHeader
import com.kunzisoft.keepass.database.file.PwDbHeaderV3
import com.kunzisoft.keepass.stream.LEDataOutputStream
@@ -42,18 +42,18 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
private var headerHashBlock: ByteArray? = null
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
fun getFinalKey(header: PwDbHeader): ByteArray? {
try {
val h3 = header as PwDbHeaderV3
mDatabaseV3.makeFinalKey(h3.masterSeed, h3.transformSeed, mDatabaseV3.numberKeyEncryptionRounds)
return mDatabaseV3.finalKey
} catch (e: IOException) {
throw PwDbOutputException("Key creation failed.", e)
throw DatabaseOutputException("Key creation failed.", e)
}
}
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
override fun output() {
// Before we output the header, we should sort our list of groups
// and remove any orphaned nodes that are no longer part of the tree hierarchy
@@ -74,7 +74,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
throw Exception()
}
} catch (e: Exception) {
throw PwDbOutputException("Algorithm not supported.", e)
throw DatabaseOutputException("Algorithm not supported.", e)
}
try {
@@ -86,23 +86,23 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
bos.close()
} catch (e: InvalidKeyException) {
throw PwDbOutputException("Invalid key", e)
throw DatabaseOutputException("Invalid key", e)
} catch (e: InvalidAlgorithmParameterException) {
throw PwDbOutputException("Invalid algorithm parameter.", e)
throw DatabaseOutputException("Invalid algorithm parameter.", e)
} catch (e: IOException) {
throw PwDbOutputException("Failed to output final encrypted part.", e)
throw DatabaseOutputException("Failed to output final encrypted part.", e)
}
}
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
override fun setIVs(header: PwDbHeaderV3): SecureRandom {
val random = super.setIVs(header)
random.nextBytes(header.transformSeed)
return random
}
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
override fun outputHeader(outputStream: OutputStream): PwDbHeaderV3 {
// Build header
val header = PwDbHeaderV3()
@@ -115,7 +115,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
} else if (mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish) {
header.flags = header.flags or PwDbHeaderV3.FLAG_TWOFISH
} else {
throw PwDbOutputException("Unsupported algorithm.")
throw DatabaseOutputException("Unsupported algorithm.")
}
header.version = PwDbHeaderV3.DBVER_DW
@@ -130,7 +130,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw PwDbOutputException("SHA-256 not implemented here.", e)
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
// Header checksum
@@ -138,7 +138,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
try {
headerDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw PwDbOutputException("SHA-256 not implemented here.", e)
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
var nos = NullOutputStream()
@@ -151,7 +151,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
pho.outputEnd()
headerDos.flush()
} catch (e: IOException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
}
val headerHash = headerDigest.digest()
@@ -166,7 +166,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
bos.flush()
bos.close()
} catch (e: IOException) {
throw PwDbOutputException("Failed to generate checksum.", e)
throw DatabaseOutputException("Failed to generate checksum.", e)
}
header.contentsHash = messageDigest!!.digest()
@@ -181,14 +181,14 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
pho.outputEnd()
dos.flush()
} catch (e: IOException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
}
return header
}
@Suppress("CAST_NEVER_SUCCEEDS")
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
fun outputPlanGroupAndEntries(os: OutputStream) {
val los = LEDataOutputStream(os)
@@ -199,7 +199,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
los.writeInt(headerHashBlock!!.size)
los.write(headerHashBlock!!)
} catch (e: IOException) {
throw PwDbOutputException("Failed to output header hash.", e)
throw DatabaseOutputException("Failed to output header hash.", e)
}
}
@@ -209,7 +209,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
try {
pgo.output()
} catch (e: IOException) {
throw PwDbOutputException("Failed to output a tree", e)
throw DatabaseOutputException("Failed to output a tree", e)
}
}
mDatabaseV3.doForEachEntryInIndex { entry ->
@@ -217,7 +217,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw
try {
peo.output()
} catch (e: IOException) {
throw PwDbOutputException("Failed to output an entry.", e)
throw DatabaseOutputException("Failed to output an entry.", e)
}
}
}

View File

@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.*
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.PwDbOutputException
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm
import com.kunzisoft.keepass.database.file.PwDbHeaderV4
@@ -63,14 +63,14 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
private var headerHmac: ByteArray? = null
private var engine: CipherEngine? = null
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
override fun output() {
try {
try {
engine = CipherFactory.getInstance(mDatabaseV4.dataCipher)
} catch (e: NoSuchAlgorithmException) {
throw PwDbOutputException("No such cipher", e)
throw DatabaseOutputException("No such cipher", e)
}
header = outputHeader(mOS)
@@ -104,13 +104,13 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
outputDatabase(osXml)
osXml.close()
} catch (e: IllegalArgumentException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
} catch (e: IllegalStateException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
}
} catch (e: IOException) {
throw PwDbOutputException(e)
throw DatabaseOutputException(e)
}
}
@@ -228,7 +228,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
xml.endTag(null, PwDatabaseV4XML.ElemMeta)
}
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
private fun attachStreamEncryptor(header: PwDbHeaderV4, os: OutputStream): CipherOutputStream {
val cipher: Cipher
try {
@@ -236,13 +236,13 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
cipher = engine!!.getCipher(Cipher.ENCRYPT_MODE, mDatabaseV4.finalKey!!, header.encryptionIV)
} catch (e: Exception) {
throw PwDbOutputException("Invalid algorithm.", e)
throw DatabaseOutputException("Invalid algorithm.", e)
}
return CipherOutputStream(os, cipher)
}
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
override fun setIVs(header: PwDbHeaderV4): SecureRandom {
val random = super.setIVs(header)
random.nextBytes(header.masterSeed)
@@ -275,7 +275,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
if (randomStream == null) {
throw PwDbOutputException("Invalid random cipher")
throw DatabaseOutputException("Invalid random cipher")
}
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
@@ -285,7 +285,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
return random
}
@Throws(PwDbOutputException::class)
@Throws(DatabaseOutputException::class)
override fun outputHeader(outputStream: OutputStream): PwDbHeaderV4 {
val header = PwDbHeaderV4(mDatabaseV4)
@@ -295,7 +295,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
try {
pho.output()
} catch (e: IOException) {
throw PwDbOutputException("Failed to output the header.", e)
throw DatabaseOutputException("Failed to output the header.", e)
}
hashOfHeader = pho.hashOfHeader

View File

@@ -22,18 +22,23 @@ package com.kunzisoft.keepass.settings
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.res.Resources
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.autofill.AutofillManager
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.biometric.BiometricManager
import androidx.fragment.app.DialogFragment
import androidx.preference.*
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
@@ -41,12 +46,13 @@ import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preference.DialogColorPreference.Companion.DISABLE_COLOR
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener {
@@ -56,6 +62,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
private var mCount = 0
private var dbCustomColorPref: DialogColorPreference? = null
private var mRoundPref: InputKdfNumberPreference? = null
private var mMemoryPref: InputKdfNumberPreference? = null
private var mParallelismPref: InputKdfNumberPreference? = null
@@ -339,11 +346,11 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
if (mDatabase.loaded) {
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_general_key))
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key))
// Db name
// Database name
val dbNamePref: InputTextPreference? = findPreference(getString(R.string.database_name_key))
if (mDatabase.containsName()) {
if (mDatabase.allowName) {
dbNamePref?.summary = mDatabase.name
} else {
dbGeneralPrefCategory?.removePreference(dbNamePref)
@@ -351,32 +358,66 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
// Database description
val dbDescriptionPref: InputTextPreference? = findPreference(getString(R.string.database_description_key))
if (mDatabase.containsDescription()) {
if (mDatabase.allowDescription) {
dbDescriptionPref?.summary = mDatabase.description
} else {
dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
}
// Database compression
findPreference<Preference>(getString(R.string.database_data_compression_key))
?.summary = (mDatabase.compressionAlgorithm ?: PwCompressionAlgorithm.None).getName(resources)
// Recycle bin
val recycleBinPref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_key))
// TODO Recycle
dbGeneralPrefCategory?.removePreference(recycleBinPref) // To delete
if (mDatabase.isRecycleBinAvailable) {
recycleBinPref?.isChecked = mDatabase.isRecycleBinEnabled
recycleBinPref?.isEnabled = false
// Database default username
val dbDefaultUsername: InputTextPreference? = findPreference(getString(R.string.database_default_username_key))
if (mDatabase.allowDefaultUsername) {
dbDefaultUsername?.summary = mDatabase.defaultUsername
} else {
dbGeneralPrefCategory?.removePreference(recycleBinPref)
dbDefaultUsername?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
}
// Database custom color
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
if (mDatabase.allowCustomColor) {
dbCustomColorPref?.apply {
try {
color = Color.parseColor(mDatabase.customColor)
summary = mDatabase.customColor
} catch (e: Exception) {
color = DISABLE_COLOR
summary = ""
}
}
} else {
dbCustomColorPref?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
}
// Version
findPreference<Preference>(getString(R.string.database_version_key))
?.summary = mDatabase.getVersion()
?.summary = mDatabase.version
findPreference<PreferenceCategory>(getString(R.string.database_history_key))
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_compression_key))
// Database compression
val databaseDataCompressionPref = findPreference<Preference>(getString(R.string.database_data_compression_key))
if (mDatabase.allowDataCompression) {
databaseDataCompressionPref?.summary = (mDatabase.compressionAlgorithm
?: PwCompressionAlgorithm.None).getName(resources)
} else {
dbCompressionPrefCategory?.isVisible = false
}
val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key))
// Recycle bin
val recycleBinPref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_key))
if (mDatabase.allowRecycleBin) {
recycleBinPref?.isChecked = mDatabase.isRecycleBinEnabled
// TODO Recycle Bin
recycleBinPref?.isEnabled = false
} else {
dbRecycleBinPrefCategory?.isVisible = false
}
findPreference<PreferenceCategory>(getString(R.string.database_category_history_key))
?.isVisible = mDatabase.manageHistory == true
// Max history items
@@ -427,7 +468,8 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
fragmentManager?.let { fragmentManager ->
AssignMasterKeyDialogFragment().show(fragmentManager, "passwordDialog")
AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey)
.show(fragmentManager, "passwordDialog")
}
false
}
@@ -488,6 +530,27 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
}
}
private val colorSelectedListener: ((Boolean, Int)-> Unit)? = { enable, color ->
dbCustomColorPref?.summary = ChromaUtil.getFormattedColorString(color, false)
if (enable) {
dbCustomColorPref?.color = color
} else {
dbCustomColorPref?.color = DISABLE_COLOR
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
try {
// To reassign color listener after orientation change
val chromaDialog = fragmentManager?.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
chromaDialog?.onColorSelectedListener = colorSelectedListener
} catch (e: Exception) {}
return view
}
override fun onDisplayPreferenceDialog(preference: Preference?) {
var otherDialogFragment = false
@@ -502,6 +565,14 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
preference.key == getString(R.string.database_description_key) -> {
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.database_default_username_key) -> {
dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.database_custom_color_key) -> {
dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply {
onColorSelectedListener = colorSelectedListener
}
}
preference.key == getString(R.string.database_data_compression_key) -> {
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
}
@@ -536,7 +607,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
if (dialogFragment != null && !mDatabaseReadOnly) {
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(fragmentManager, null)
dialogFragment.show(fragmentManager, TAG_PREF_FRAGMENT)
}
// Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
@@ -560,6 +631,8 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
private const val TAG_KEY = "NESTED_KEY"
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
private const val REQUEST_CODE_AUTOFILL = 5201
@JvmOverloads

View File

@@ -33,6 +33,12 @@ object PreferencesUtil {
return prefs.getBoolean(context.getString(R.string.show_read_only_warning), true)
}
fun rememberKeyFiles(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.keyfile_key),
context.resources.getBoolean(R.bool.keyfile_default))
}
fun omitBackup(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.omitbackup_key),

View File

@@ -115,7 +115,7 @@ open class SettingsActivity
true)
}
// Show the progress dialog now or after dialog confirmation
if (database.validatePasswordEncoding(masterPassword)) {
if (database.validatePasswordEncoding(masterPassword, keyFileChecked)) {
progressDialogThread.start()
} else {
PasswordEncodingDialogFragment().apply {

View File

@@ -0,0 +1,33 @@
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import androidx.annotation.ColorInt
import com.kunzisoft.androidclearchroma.ChromaPreferenceCompat
import com.kunzisoft.keepass.R
class DialogColorPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: ChromaPreferenceCompat(context, attrs, defStyleAttr, defStyleRes) {
override fun setSummary(summary: CharSequence?) {
if (color == DISABLE_COLOR)
super.setSummary("")
else
super.setSummary(summary)
}
override fun getDialogLayoutResource(): Int {
return R.layout.pref_dialog_input_color
}
companion object {
@ColorInt
const val DISABLE_COLOR: Int = Color.TRANSPARENT
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.app.Dialog
import android.graphics.Color
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.CompoundButton
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.androidclearchroma.IndicatorMode
import com.kunzisoft.androidclearchroma.colormode.ColorMode
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment.*
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.tasks.ActionRunnable
import java.lang.Exception
class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
private lateinit var rootView: View
private lateinit var enableSwitchView: CompoundButton
private var chromaColorFragment: ChromaColorFragment? = null
var onColorSelectedListener: ((enable: Boolean, color: Int) -> Unit)? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val alertDialogBuilder = AlertDialog.Builder(activity!!)
rootView = activity!!.layoutInflater.inflate(R.layout.pref_dialog_input_color, null)
enableSwitchView = rootView.findViewById(R.id.switch_element)
val fragmentManager = childFragmentManager
chromaColorFragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_COLORS) as ChromaColorFragment?
val fragmentTransaction = fragmentManager.beginTransaction()
database?.let { database ->
val initColor = try {
enableSwitchView.isChecked = true
Color.parseColor(database.customColor)
} catch (e: Exception) {
enableSwitchView.isChecked = false
DEFAULT_COLOR
}
arguments?.putInt(ARG_INITIAL_COLOR, initColor)
}
if (chromaColorFragment == null) {
chromaColorFragment = newInstance(arguments)
fragmentTransaction.add(com.kunzisoft.androidclearchroma.R.id.color_dialog_container, chromaColorFragment!!, TAG_FRAGMENT_COLORS).commit()
}
alertDialogBuilder.setPositiveButton(android.R.string.ok) { _, _ ->
val currentColor = chromaColorFragment!!.currentColor
val customColorEnable = enableSwitchView.isChecked
onColorSelectedListener?.invoke(customColorEnable, currentColor)
database?.let { database ->
val newColor = if (customColorEnable) {
ChromaUtil.getFormattedColorString(currentColor, false)
} else {
""
}
val oldColor = database.customColor
database.customColor = newColor
actionInUIThreadAfterSaveDatabase = AfterColorSave(newColor, oldColor)
}
super.onDialogClosed(true)
dismiss()
}
alertDialogBuilder.setNegativeButton(android.R.string.cancel) { _, _ ->
super.onDialogClosed(false)
dismiss()
}
alertDialogBuilder.setView(rootView)
val dialog = alertDialogBuilder.create()
// request a window without the title
dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
dialog.setOnShowListener { measureLayout(it as Dialog) }
return dialog
}
/**
* Set new dimensions to dialog
* @param ad dialog
*/
private fun measureLayout(ad: Dialog) {
val typedValue = TypedValue()
resources.getValue(com.kunzisoft.androidclearchroma.R.dimen.chroma_dialog_height_multiplier, typedValue, true)
val heightMultiplier = typedValue.float
val height = (ad.context.resources.displayMetrics.heightPixels * heightMultiplier).toInt()
resources.getValue(com.kunzisoft.androidclearchroma.R.dimen.chroma_dialog_width_multiplier, typedValue, true)
val widthMultiplier = typedValue.float
val width = (ad.context.resources.displayMetrics.widthPixels * widthMultiplier).toInt()
ad.window?.setLayout(width, height)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return rootView
}
private inner class AfterColorSave(private val mNewColor: String,
private val mOldColor: String)
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val defaultColorToShow =
if (result.isSuccess) {
mNewColor
} else {
database?.customColor = mOldColor
mOldColor
}
preference.summary = defaultColorToShow
}
}
companion object {
private const val TAG_FRAGMENT_COLORS = "TAG_FRAGMENT_COLORS"
@ColorInt
const val DEFAULT_COLOR: Int = Color.WHITE
fun newInstance(key: String): DatabaseColorPreferenceDialogFragmentCompat {
val fragment = DatabaseColorPreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
bundle.putInt(ARG_INITIAL_COLOR, Color.BLACK)
bundle.putInt(ARG_COLOR_MODE, ColorMode.RGB.ordinal)
bundle.putInt(ARG_INDICATOR_MODE, IndicatorMode.HEX.ordinal)
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -62,9 +62,7 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
if (compressionSelected != null) {
val newAlgorithm = compressionSelected
val oldAlgorithm = database.compressionAlgorithm
newAlgorithm?.let {
database.assignCompressionAlgorithm(it)
}
database.compressionAlgorithm = newAlgorithm
if (oldAlgorithm != null && newAlgorithm != null)
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newAlgorithm, oldAlgorithm)
@@ -88,7 +86,7 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
if (result.isSuccess) {
mNewAlgorithm
} else {
database?.assignCompressionAlgorithm(mOldAlgorithm)
database?.compressionAlgorithm = mOldAlgorithm
mOldAlgorithm
}
preference.summary = algorithmToShow.getName(settingsResources)

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.tasks.ActionRunnable
class DatabaseDefaultUsernamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
inputText = database?.defaultUsername?: ""
}
override fun onDialogClosed(positiveResult: Boolean) {
database?.let { database ->
if (positiveResult) {
val newDefaultUsername = inputText
val oldDefaultUsername = database.defaultUsername
database.defaultUsername = newDefaultUsername
actionInUIThreadAfterSaveDatabase = AfterDefaultUsernameSave(newDefaultUsername, oldDefaultUsername)
}
}
super.onDialogClosed(positiveResult)
}
private inner class AfterDefaultUsernameSave(private val mNewDefaultUsername: String,
private val mOldDefaultUsername: String)
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val defaultUsernameToShow =
if (result.isSuccess) {
mNewDefaultUsername
} else {
database?.defaultUsername = mOldDefaultUsername
mOldDefaultUsername
}
preference.summary = defaultUsernameToShow
}
}
companion object {
fun newInstance(key: String): DatabaseDefaultUsernamePreferenceDialogFragmentCompat {
val fragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -32,12 +32,14 @@ class DatabaseDescriptionPreferenceDialogFragmentCompat : DatabaseSavePreference
}
override fun onDialogClosed(positiveResult: Boolean) {
if (database != null && positiveResult) {
val newDescription = inputText
val oldDescription = database!!.description
database?.assignDescription(newDescription)
database?.let { database ->
if (positiveResult) {
val newDescription = inputText
val oldDescription = database.description
database.description = newDescription
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newDescription, oldDescription)
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newDescription, oldDescription)
}
}
super.onDialogClosed(positiveResult)
@@ -52,7 +54,7 @@ class DatabaseDescriptionPreferenceDialogFragmentCompat : DatabaseSavePreference
if (result.isSuccess) {
mNewDescription
} else {
database?.assignDescription(mOldDescription)
database?.description = mOldDescription
mOldDescription
}
preference.summary = descriptionToShow

View File

@@ -59,13 +59,11 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
if (positiveResult) {
database?.let { database ->
if (database.allowEncryptionAlgorithmModification()) {
if (database.allowEncryptionAlgorithmModification) {
if (algorithmSelected != null) {
val newAlgorithm = algorithmSelected
val oldAlgorithm = database.encryptionAlgorithm
newAlgorithm?.let {
database.assignEncryptionAlgorithm(it)
}
database.encryptionAlgorithm = newAlgorithm
if (oldAlgorithm != null && newAlgorithm != null)
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newAlgorithm, oldAlgorithm)
@@ -90,7 +88,7 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
if (result.isSuccess) {
mNewAlgorithm
} else {
database?.assignEncryptionAlgorithm(mOldAlgorithm)
database?.encryptionAlgorithm = mOldAlgorithm
mOldAlgorithm
}
preference.summary = algorithmToShow.getName(settingsResources)

View File

@@ -62,11 +62,11 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
database?.let { database ->
if (database.allowKdfModification()) {
if (database.allowKdfModification) {
val newKdfEngine = kdfEngineSelected
val oldKdfEngine = database.kdfEngine
if (newKdfEngine != null && oldKdfEngine != null) {
database.assignKdfEngine(newKdfEngine)
database.kdfEngine = newKdfEngine
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newKdfEngine, oldKdfEngine)
}
}
@@ -101,7 +101,7 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
if (result.isSuccess) {
mNewKdfEngine
} else {
database?.assignKdfEngine(mOldKdfEngine)
database?.kdfEngine = mOldKdfEngine
mOldKdfEngine
}
preference.summary = kdfEngineToShow.getName(settingsResources)

View File

@@ -36,7 +36,7 @@ class DatabaseNamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogF
database?.let { database ->
val newName = inputText
val oldName = database.name
database.assignName(newName)
database.name = newName
actionInUIThreadAfterSaveDatabase = AfterNameSave(newName, oldName)
}
@@ -54,7 +54,7 @@ class DatabaseNamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogF
if (result.isSuccess) {
mNewName
} else {
database?.assignName(mOldName)
database?.name = mOldName
mOldName
}
preference.summary = nameToShow

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.content.res.Resources
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
@@ -36,10 +37,14 @@ abstract class DatabaseSavePreferenceDialogFragmentCompat : InputPreferenceDialo
protected lateinit var settingsResources: Resources
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.database = Database.getInstance()
}
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
activity?.resources?.let { settingsResources = it }
}

View File

@@ -24,6 +24,7 @@ import android.content.Context
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
/**
* Callback after a task is completed.
@@ -52,8 +53,21 @@ abstract class ActionRunnable(private var nestedActionRunnable: ActionRunnable?
* launch the nested action runnable if exists and finish,
* else directly finish
*/
protected fun finishRun(isSuccess: Boolean, message: String? = null) {
protected fun finishRun(isSuccess: Boolean,
message: String? = null) {
finishRun(isSuccess, null, message)
}
/**
* If [success] or [executeNestedActionIfResultFalse] true,
* launch the nested action runnable if exists and finish,
* else directly finish
*/
protected fun finishRun(isSuccess: Boolean,
exception: LoadDatabaseException?,
message: String? = null) {
result.isSuccess = isSuccess
result.exception = exception
result.message = message
if (isSuccess || executeNestedActionIfResultFalse) {
execute()
@@ -89,5 +103,8 @@ abstract class ActionRunnable(private var nestedActionRunnable: ActionRunnable?
/**
* Class to manage result from ActionRunnable
*/
data class Result(var isSuccess: Boolean = true, var message: String? = null, var data: Bundle? = null)
data class Result(var isSuccess: Boolean = true,
var message: String? = null,
var exception: LoadDatabaseException? = null,
var data: Bundle? = null)
}

View File

@@ -72,22 +72,39 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end|center_vertical"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:scaleType="fitXY" />
android:orientation="horizontal"
android:gravity="center_vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/group_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end|center_vertical"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:scaleType="fitXY" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_numbers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="3"
style="@style/KeepassDXStyle.TextAppearance.Info"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginLeft="@dimen/image_list_margin"
android:layout_marginStart="@dimen/image_list_margin"
android:layout_weight="1"
android:text="@string/root"
android:maxLines="1"

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX 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.
KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/edit"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<FrameLayout
android:id="@+id/color_dialog_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="@string/enable"
android:background="@drawable/background_button_small"
android:textColor="?attr/textColorInverse"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:minHeight="48dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -38,7 +38,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<androidx.appcompat.widget.AppCompatCheckBox
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@@ -38,7 +38,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<androidx.appcompat.widget.AppCompatCheckBox
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@@ -22,6 +22,6 @@
<item android:id="@+id/menu_fingerprint_remove_key"
android:icon="@drawable/ic_fingerprint_remove_white_24dp"
android:title="@string/menu_biometric_remove_key"
android:orderInCategory="93"
android:orderInCategory="85"
app:showAsAction="ifRoom" />
</menu>

View File

@@ -27,7 +27,6 @@
<string name="beta_dontask">لا تظهر مرة أخرى</string>
<string name="brackets">أقواس</string>
<string name="extended_ASCII">تمديد ASCII</string>
<string name="cancel">إلغاء</string>
<string name="allow">السماح</string>
<string name="clipboard_cleared">مُسِحت الحافظة</string>
<string name="clipboard_error_title">خطأ في الحافظة</string>
@@ -54,7 +53,6 @@
<string name="error_title_required">اكتب عنوانًا.</string>
<string name="field_name">اسم الحقل</string>
<string name="field_value">قيمة الحقل</string>
<string name="file_not_found">تعذر إيجاد الملف.</string>
<string name="generate_password">توليد كلمة سر</string>
<string name="hint_conf_pass">تأكيد كلمة السر</string>
<string name="hint_group_name">اسم المجموعة</string>

View File

@@ -34,7 +34,6 @@
<string name="menu_app_settings">Paràmetres de l\'aplicació</string>
<string name="brackets">Parèntesis</string>
<string name="file_manager_install_description">L\'exploració d\'arxius necessita l\'aplicació Open Intents File Manager, clica a sota per instal·lar-la. Degut a peculiaritats de l\'explorador d\'arxius pot ser que no funcioni correctament la primera execució.</string>
<string name="cancel">Cancel·la</string>
<string name="clipboard_cleared">Porta-retalls netejat.</string>
<string name="clipboard_timeout">Temps d\'espera del porta-retalls</string>
<string name="clipboard_timeout_summary">Temps abans de netejar el porta-retalls després de copiar un usuari o contrasenya</string>
@@ -72,7 +71,6 @@
<string name="error_rounds_too_large">Massa passades. Establint a 2147483648.</string>
<string name="error_title_required">És necessari un títol.</string>
<string name="error_wrong_length">Insereix un enter positiu al camp longitud</string>
<string name="file_not_found">Arxiu no trobat.</string>
<string name="file_browser">Explorador d\'arxius</string>
<string name="generate_password">Generar contrasenya</string>
<string name="hint_conf_pass">confirma contrasenya</string>

View File

@@ -34,7 +34,6 @@
<string name="beta_dontask">Znovu neukázat</string>
<string name="brackets">Závorky</string>
<string name="file_manager_install_description">Instalace správce souborů OpenIntents k procházení souborů</string>
<string name="cancel">Storno</string>
<string name="clipboard_cleared">Schránka vyčištěna</string>
<string name="clipboard_error_title">Chyba schránky</string>
<string name="clipboard_error">Některé Android telefony od Samsungu nedovolují aplikacím používat schránku.</string>
@@ -79,7 +78,6 @@
<string name="error_wrong_length">Do nastavení „Délka“ zadejte celé kladné číslo.</string>
<string name="field_name">Název pole</string>
<string name="field_value">Hodnota pole</string>
<string name="file_not_found">Soubor nenalezen.</string>
<string name="file_browser">Správce souborů</string>
<string name="generate_password">Vytvoř heslo</string>
<string name="hint_conf_pass">potvrď heslo</string>

View File

@@ -34,7 +34,6 @@
<string name="beta_dontask">Vis ikke igen</string>
<string name="brackets">Parenteser</string>
<string name="file_manager_install_description">Installer OpenIntents Fil Manager for at gennemse filer</string>
<string name="cancel">Annuller</string>
<string name="clipboard_cleared">Udklipsholder ryddet</string>
<string name="clipboard_error_title">Udklipsfejl</string>
<string name="clipboard_error">Nogle Samsung Android-telefoner, vil ikke lade programmer bruge udklipsholderen.</string>
@@ -78,7 +77,6 @@
<string name="error_wrong_length">Angiv et positivt heltal i feltet \"Længde\".</string>
<string name="field_name">Feltnavn</string>
<string name="field_value">Feltværdi</string>
<string name="file_not_found">Kunne ikke finde filen.</string>
<string name="file_browser">Filhåndtering</string>
<string name="generate_password">Generer adgangskode</string>
<string name="hint_conf_pass">bekræft adgangskode</string>

View File

@@ -36,7 +36,6 @@
<string name="beta_dontask">Nicht mehr anzeigen</string>
<string name="brackets">Klammern</string>
<string name="file_manager_install_description">Durchsuchen Sie Ihre Dateien, indem Sie den OpenIntents File Manager installieren</string>
<string name="cancel">Abbrechen</string>
<string name="clipboard_cleared">Zwischenablage geleert</string>
<string name="clipboard_error_title">Zwischenablagefehler</string>
<string name="clipboard_error">Einige Samsung Android-Smartphones lassen keine Nutzung der Zwischenablage durch Apps zu.</string>
@@ -81,7 +80,6 @@
<string name="error_wrong_length">Eine positive ganze Zahl in das Feld „Länge“ eingeben.</string>
<string name="field_name">Feldname</string>
<string name="field_value">Feldwert</string>
<string name="file_not_found">Datei nicht gefunden.</string>
<string name="file_not_found_content">Datei nicht gefunden. Bitte versuchen, sie über den Dateimanager zu öffnen.</string>
<string name="file_browser">Dateimanager</string>
<string name="generate_password">Passwort generieren</string>

View File

@@ -32,7 +32,6 @@
<string name="beta_dontask">Να μην ερωτηθώ ξανά</string>
<string name="brackets">Αγκύλες</string>
<string name="file_manager_install_description">Η αναζήτηση αρχείων απαιτεί τον Διαχειριστή Αρχείων Open Intents, πατήστε παρακάτω για να τον εγκαταστήσετε. Λόγω μερικών ιδιορρυθμιών στον διαχειριστή αρχείων, η περιήγηση μπορεί να μην λειτουργεί σωστά την πρώτη φορά που θα περιηγηθείτε.</string>
<string name="cancel">Ακύρωση</string>
<string name="clipboard_cleared">Το πρόχειρο καθαρίστηκε.</string>
<string name="clipboard_error_title">Σφάλμα προχείρου</string>
<string name="clipboard_error">Μερικά Android κινητά τηλέφωνα της Samsung έχουν ένα σφάλμα στην εφαρμογή του προχείρου που προκαλεί την αντιγραφή από εφαρμογές να αποτυγχάνει. Για περισσότερες πληροφορίες πηγαίνετε:</string>
@@ -77,7 +76,6 @@
<string name="error_wrong_length">Εισάγετε έναν θετικό ακέραιο αριθμό στο πεδίο μήκους</string>
<string name="field_name">Όνομα Πεδίου</string>
<string name="field_value">Τιμή πεδίου</string>
<string name="file_not_found">Το αρχείο δεν βρέθηκε.</string>
<string name="file_browser">Διαχείριση Αρχείων</string>
<string name="generate_password">Δημιουργία Κωδικού</string>
<string name="hint_conf_pass">επιβεβαίωση κωδικού</string>

View File

@@ -33,7 +33,6 @@
<string name="menu_app_settings">Configuración de la aplicación</string>
<string name="brackets">Paréntesis</string>
<string name="file_manager_install_description">Explora ficheros con OpenIntents File Manager</string>
<string name="cancel">Cancelar</string>
<string name="clipboard_cleared">Portapapeles limpiado</string>
<string name="clipboard_timeout">Portapapeles caducado</string>
<string name="clipboard_timeout_summary">Duración de almacemiento en el portapapeles</string>
@@ -71,7 +70,6 @@
<string name="error_rounds_too_large">Pasadas demasiado grande. Establecido a 2147483648.</string>
<string name="error_title_required">Se necesita un título.</string>
<string name="error_wrong_length">Introduzca un entero positivo en el campo longitud</string>
<string name="file_not_found">Archivo no encontrado.</string>
<string name="file_browser">Explorador de Archivos</string>
<string name="generate_password">Generar Contraseña</string>
<string name="hint_conf_pass">confirmar contraseña</string>

View File

@@ -33,7 +33,6 @@
<string name="beta_dontask">Ez erakutsi berriro</string>
<string name="brackets">Brackets</string>
<string name="file_manager_install_description">Fitxategietan nabigatzeak Open Intents Fitxategi Kudeatzailea behar du. Klik egin azpian instalatzeko. Fitxategien kudeatzailaren arazo batzuk direla eta, izan daiteke nabigazioak ondo ez funtzionatzea lehenengo aldian.</string>
<string name="cancel">Utzi</string>
<string name="clipboard_cleared">Arbela ezabatuta.</string>
<string name="clipboard_error_title">Arbelean errorea</string>
<string name="clipboard_error">Samsung Android telefono batzuek akats bat daukate arbelaren inplementazioan, eta honen ondorioz aplikazioetatik kopiatzeak huts egiten du. Xehetasun gehiagotarako ondokora joan:</string>
@@ -78,7 +77,6 @@
<string name="error_wrong_length">Eremuaren luzeran entero positibo bat sartu</string>
<string name="field_name">Eremuaren izena</string>
<string name="field_value">Eremuaren balorea</string>
<string name="file_not_found">Fitxategi ez aurkitua.</string>
<string name="file_browser">Fitxategien nabigatzailea</string>
<string name="generate_password">Pasahitza sortu</string>
<string name="hint_conf_pass">pasahitza berretsi</string>

View File

@@ -32,7 +32,6 @@
<string name="beta_dontask">Älä näytä enää uudelleen</string>
<string name="brackets">Hakasulkeet</string>
<string name="file_manager_install_description">Tiedostojen selaus vaatii Open Intents File Manager -tiedostonhallintaohjelman, klikkaa alla olevaa linkkiä asentaaksesi sen. Joidenkin ominaisuuksien takia se ei ehkä toimi oikein ensimmäisellä käynnistyksellä.</string>
<string name="cancel">Peruuta</string>
<string name="clipboard_cleared">Leikepöytä tyhjennetty.</string>
<string name="clipboard_error_title">Leikepöytävirhe</string>
<string name="clipboard_error">Joissakin Android-puhelimissa on virhe leikepöydän toteutuksessa, mikä aiheuttaa kopioinnin epäonnistumisen. Lisätietoa:</string>
@@ -77,7 +76,6 @@
<string name="error_wrong_length">Syötä positiivinen kokonaisluku pituus-kenttään</string>
<string name="field_name">Kentän nimi</string>
<string name="field_value">Kentän arvo</string>
<string name="file_not_found">Tiedostoa ei löydetty.</string>
<string name="file_browser">Tiedostoselain</string>
<string name="generate_password">Generoi salasana</string>
<string name="hint_conf_pass">vahvista salasana</string>

View File

@@ -36,7 +36,6 @@
<string name="brackets">Crochets</string>
<string name="extended_ASCII">ASCII étendu</string>
<string name="file_manager_install_description">Parcourir les fichiers en installant le gestionnaire de fichiers OpenIntents</string>
<string name="cancel">Annuler</string>
<string name="allow">Autoriser</string>
<string name="clipboard_cleared">Presse-papier vidé</string>
<string name="clipboard_error_title">Erreur de presse-papier</string>
@@ -83,7 +82,6 @@
<string name="error_autofill_enable_service">Impossible dactiver le service de remplissage automatique.</string>
<string name="field_name">Nom du champ</string>
<string name="field_value">Valeur du champ</string>
<string name="file_not_found">Impossible de trouver le fichier.</string>
<string name="file_not_found_content">Impossible de trouver le fichier. Essayer de le rouvrir depuis votre gestionnaire de fichiers.</string>
<string name="file_browser">Gestionnaire de fichiers</string>
<string name="generate_password">Générer un mot de passe</string>

View File

@@ -14,7 +14,6 @@
<string name="beta_dontask">Non amosar de novo</string>
<string name="brackets">Parénteses</string>
<string name="extended_ASCII">ASCII extendido</string>
<string name="cancel">Cancelar</string>
<string name="allow">Permitir</string>
<string name="clipboard_cleared">Portapapeis limpo</string>
<string name="clipboard_error_title">Erro do portapapeis</string>

View File

@@ -32,7 +32,6 @@
<string name="beta_dontask">"Ne mutassa többet"</string>
<string name="brackets">Zárójelek</string>
<string name="file_manager_install_description">Fájlok böngészése az OpenIntents fájlkezelő telepítésével</string>
<string name="cancel">Mégse</string>
<string name="clipboard_cleared">Vágólap törölve</string>
<string name="clipboard_error_title">Vágólap hiba</string>
<string name="clipboard_error">Egyes androidos Samsung telefonok nem engedik, hogy az alkalmazások használják a vágólapot.</string>
@@ -76,7 +75,6 @@
<string name="error_wrong_length">Írjon be egy pozitív egész számot a „Hossz” mezőbe.</string>
<string name="field_name">Mezőnév</string>
<string name="field_value">Mezőérték</string>
<string name="file_not_found">A fájl nem található.</string>
<string name="file_not_found_content">A fájl nem található. Próbálja meg újra megnyitni a fájlkezelőben.</string>
<string name="file_browser">Fájlkezelő</string>
<string name="generate_password">Jelszó előállítása</string>

View File

@@ -33,7 +33,6 @@
<string name="menu_app_settings">Impostazioni app</string>
<string name="brackets">Parentesi</string>
<string name="file_manager_install_description">Sfoglia i file installando il Gestore File di OpenIntents</string>
<string name="cancel">Annulla</string>
<string name="clipboard_cleared">Appunti eliminati</string>
<string name="clipboard_error_title">Errore negli appunti</string>
<string name="clipboard_error">Alcuni telefoni Android di Samsung non permettono alle app di usare gli appunti.</string>
@@ -77,7 +76,6 @@
<string name="error_wrong_length">Inserisci un numero naturale positivo nel campo \"lunghezza\".</string>
<string name="field_name">Nome campo</string>
<string name="field_value">Valore campo</string>
<string name="file_not_found">File non trovato.</string>
<string name="file_not_found_content">File non trovato. Prova a riaprirlo dal tuo gestore di file.</string>
<string name="file_browser">Gestore file</string>
<string name="generate_password">Genera password</string>

View File

@@ -31,7 +31,6 @@
<string name="beta_dontask">אל תציג שוב</string>
<string name="brackets">סוגריים</string>
<string name="file_manager_install_description">סייר הקבצים דורש את סייר הקבצים Open Intents, לחץ למטע כדי להתקין. בגלל מספר בעיות בסייר, ייתכן ויהיו בעיות בהפעלה הראשונה.</string>
<string name="cancel">בטל</string>
<string name="clipboard_cleared">לוח ההעתקה נוקה.</string>
<string name="clipboard_error_title">שגיאת לוח ההעתקה</string>
<string name="clipboard_error">במספר מכשירי אנרואיד מסמסונג קיים באג במימוש לוח ההעתקה שיכול לגרום לבעיות בהעתקה מהיישום. לעוד מידע עבור אל:</string>
@@ -74,7 +73,6 @@
<string name="error_wrong_length">הזן מספר חיובי בשדה האורך</string>
<string name="field_name">שם השדה</string>
<string name="field_value">ערך השדה</string>
<string name="file_not_found">קובץ לא נמצא.</string>
<string name="file_browser">סייר קבצים</string>
<string name="generate_password">צור סיסמה</string>
<string name="hint_conf_pass">אשר סיסמה</string>

View File

@@ -30,7 +30,6 @@
<string name="menu_app_settings">アプリケーション設定</string>
<string name="brackets">カッコ</string>
<string name="file_manager_install_description">ファイルを検索するには OI File Manager が必要です。</string>
<string name="cancel">キャンセル</string>
<string name="clipboard_cleared">クリップボードを消去しました。</string>
<string name="clipboard_timeout">クリップボード タイムアウト</string>
<string name="clipboard_timeout_summary">コピーした情報をクリップボードから消去する時間</string>
@@ -68,7 +67,6 @@
<string name="error_rounds_too_large">値が大きすぎます。 2147483648にセットしました。</string>
<string name="error_title_required">タイトルは必須入力です。</string>
<string name="error_wrong_length">\"長さ\"欄には正の整数を入力してください。</string>
<string name="file_not_found">ファイルが見つかりません。</string>
<string name="file_browser">ファイルブラウザ</string>
<string name="generate_password">パスワードを生成する</string>
<string name="hint_conf_pass">パスワードをもう一度入力</string>

View File

@@ -35,7 +35,6 @@
<string name="brackets">브라켓</string>
<string name="extended_ASCII">확장 ASCII</string>
<string name="file_manager_install_description">OpenIntents File Manager를 설치하여 파일 찾아보기</string>
<string name="cancel">취소</string>
<string name="allow">허가</string>
<string name="clipboard_cleared">클립보드 비워짐</string>
<string name="clipboard_error_title">클립보드 오류</string>
@@ -85,7 +84,6 @@
<string name="error_move_folder_in_itself">그룹을 자신에게 옮길 수 없습니다.</string>
<string name="field_name">필드 이름</string>
<string name="field_value">필드 값</string>
<string name="file_not_found">파일을 찾을 수 없습니다.</string>
<string name="file_not_found_content">파일을 찾을 수 없습니다. 파일 탐색기에서 열리는지 확인해 주세요.</string>
<string name="file_browser">파일 탐색기</string>
<string name="generate_password">비밀번호 생성</string>

View File

@@ -2,7 +2,6 @@
<resources>
<string name="about_description">KeePass DX yra KeePass slaptažodžių tvarkyklės realizacija Android platformai</string>
<string name="clipboard_cleared">Iškarpinė išvalyta.</string>
<string name="file_not_found">Failas nerastas.</string>
<string name="invalid_password">Neteisingas slaptažodis arba rakto failas.</string>
<string name="about_feedback">Atsiliepimai:</string>
<string name="about_homepage">Pagrindinis puslapis:</string>
@@ -15,7 +14,6 @@
<string name="menu_app_settings">Programėlės nustatymai</string>
<string name="beta_dontask">Daugiau neberodyti</string>
<string name="brackets">Skliaustai</string>
<string name="cancel">Atšaukti</string>
<string name="database">Duomenų bazė</string>
<string name="digits">Skaitmenys</string>
<string name="entry_cancel">Atšaukti</string>

View File

@@ -14,7 +14,6 @@
<string name="beta_dontask">Turpmāk nerādīt</string>
<string name="brackets">Iekavas</string>
<string name="file_manager_install_description">Failu pārlūkošanai nepieciešams pārlūks.</string>
<string name="cancel">Atcelt</string>
<string name="clipboard_cleared">Starpliktuve notīrīta</string>
<string name="clipboard_error_title">Starpliktuves kļūda</string>
<string name="clipboard_error">Dažiem Samsung tālruņiem ir problēmas ar starpliktuves lietošanu. Lai saņemtu sīkāku informāciju, dodieties uz:</string>
@@ -57,7 +56,6 @@
<string name="error_wrong_length">Norādiet garumu lielāku par nulli</string>
<string name="field_name">Lauka nosaukums</string>
<string name="field_value">Lauka vērtība</string>
<string name="file_not_found">Fails nav atrasts.</string>
<string name="file_browser">Failu pārlūks</string>
<string name="generate_password">Ģenerēt Paroli</string>
<string name="hint_conf_pass">apstipriniet paroli</string>

View File

@@ -35,7 +35,6 @@
<string name="brackets">Parenteser</string>
<string name="extended_ASCII">Utvidet ASCII</string>
<string name="file_manager_install_description">Utforsk filer ved å installere OpenIntents-filbehandleren</string>
<string name="cancel">Avbryt</string>
<string name="allow">Tillat</string>
<string name="clipboard_cleared">Utklippstavle tømt</string>
<string name="clipboard_error_title">Utklippstavlefeil</string>
@@ -85,7 +84,6 @@
<string name="error_move_folder_in_itself">Kan ikke flytte gruppe inn i seg selv.</string>
<string name="field_name">Feltnavn</string>
<string name="field_value">Feltverdi</string>
<string name="file_not_found">Fant ikke filen.</string>
<string name="file_not_found_content">Fant ikke filen. Prøv å åpne den fra din innholdsleverandør.</string>
<string name="file_browser">Filutforsker</string>
<string name="generate_password">Opprett passord</string>

View File

@@ -33,7 +33,6 @@
<string name="menu_app_settings">App-instellingen</string>
<string name="brackets">Haakjes</string>
<string name="file_manager_install_description">Zoek een bestand op door het installeren van de OpenIntents File Manager</string>
<string name="cancel">Annuleren</string>
<string name="clipboard_cleared">Klembord gewist</string>
<string name="clipboard_timeout">Klembordtime-out</string>
<string name="clipboard_timeout_summary">Tijd van opslag op het klembord</string>
@@ -71,7 +70,6 @@
<string name="error_rounds_too_large">\"Cycli-waarde\" te groot. Wordt ingesteld op 2147483648.</string>
<string name="error_title_required">Voeg een titel toe.</string>
<string name="error_wrong_length">Voer een positief geheel getal in in het veld \"Lengte\".</string>
<string name="file_not_found">Bestand niet gevonden.</string>
<string name="file_browser">Bestandsverkenner</string>
<string name="generate_password">Wachtwoord genereren</string>
<string name="hint_conf_pass">wachtwoord bevestigen</string>

View File

@@ -31,7 +31,6 @@
<string name="menu_app_settings">Programinnstillingar</string>
<string name="brackets">Parentesar</string>
<string name="file_manager_install_description">Du må ha Open Intents filbehandlar for å kunna bla i filer. Klikk nedanfor for å installera han. Grunna nokre småfeil i programmet kan det vera at det ikkje fungerer heilt den første gongen du bruker det.</string>
<string name="cancel">Avbryt</string>
<string name="clipboard_cleared">Utklippstavla er tømt.</string>
<string name="clipboard_timeout">Tidsavbrot på utklippstavla</string>
<string name="clipboard_timeout_summary">Tid før utklippstavla blir tømt etter at brukarnamnet eller passordet er kopiert.</string>
@@ -69,7 +68,6 @@
<string name="error_rounds_too_large">For mange omgangar. Bruker 2147483648.</string>
<string name="error_title_required">Treng ein tittel.</string>
<string name="error_wrong_length">Bruk eit positivt heiltal i lengdfeltet</string>
<string name="file_not_found">Fann ikkje fila.</string>
<string name="file_browser">Filbehandlar</string>
<string name="generate_password">Lag passord</string>
<string name="hint_conf_pass">stadfest passordet</string>

View File

@@ -31,7 +31,6 @@
<string name="menu_app_settings">Ustawienia aplikacji</string>
<string name="brackets">Nawiasy</string>
<string name="file_manager_install_description">Przeglądaj pliki, instalując Menedżera plików OpenIntents</string>
<string name="cancel">Anuluj</string>
<string name="clipboard_cleared">Schowek został wyczyszczony</string>
<string name="clipboard_timeout">Czas wygaśnięcia schowka</string>
<string name="clipboard_timeout_summary">Czas przechowywania w schowku</string>
@@ -68,7 +67,6 @@
<string name="error_rounds_too_large">\"Rundy szyfrowania\" są zbyt wysokie. Ustaw na 2147483648.</string>
<string name="error_title_required">Dodaj tytuł.</string>
<string name="error_wrong_length">Wprowadź dodatnią liczbę całkowitą w polu \"Długość\".</string>
<string name="file_not_found">Nie znaleziono pliku.</string>
<string name="file_browser">Przeglądarka plików</string>
<string name="generate_password">Generuj hasło</string>
<string name="hint_conf_pass">potwierdź hasło</string>

View File

@@ -33,7 +33,6 @@
<string name="menu_app_settings">Configurações do aplicativo</string>
<string name="brackets">Parênteses</string>
<string name="file_manager_install_description">Procure arquivos instalando o OpenIntents File Manager</string>
<string name="cancel">Cancelar</string>
<string name="clipboard_cleared">Área de transferência limpa</string>
<string name="clipboard_timeout">Tempo limite para o clipboard</string>
<string name="clipboard_timeout_summary">Duração do armazenamento na área de transferência</string>
@@ -71,7 +70,6 @@
<string name="error_rounds_too_large">\"Número de rodadas\" é muito grande. Modificado para 2147483648.</string>
<string name="error_title_required">Insira um título.</string>
<string name="error_wrong_length">Digite um número inteiro positivo no campo \"Tamanho\".</string>
<string name="file_not_found">Não pôde encontrar o arquivo.</string>
<string name="file_browser">Localizador de arquivos</string>
<string name="generate_password">Gerar senha</string>
<string name="hint_conf_pass">confirmar senha</string>

View File

@@ -32,7 +32,6 @@
<string name="beta_dontask">Não mostrar novamente</string>
<string name="brackets">Parênteses</string>
<string name="file_manager_install_description">Explore ficheiros instalando o gestor de ficheiros OpenIntents</string>
<string name="cancel">Cancelar</string>
<string name="clipboard_cleared">Área de transferência limpa</string>
<string name="clipboard_error_title">Erro na área de transferência</string>
<string name="clipboard_error">Alguns dispositivos Samsung Android não deixam as apps usarem a área de transferência.</string>
@@ -77,7 +76,6 @@
<string name="error_wrong_length">Digite um número inteiro positivo no campo \"Tamanho\".</string>
<string name="field_name">Nome do campo</string>
<string name="field_value">Valor do campo</string>
<string name="file_not_found">Não pôde encontrar o ficheiro.</string>
<string name="file_not_found_content">Arquivo não encontrado. Tente reabrí-lo de seu provedor de conteúdo.</string>
<string name="file_browser">Localizador de ficheiros</string>
<string name="generate_password">Gerar palavra-chave</string>

View File

@@ -32,7 +32,6 @@
<string name="beta_dontask">Не показывать снова</string>
<string name="brackets">{[(Скобки)]}</string>
<string name="file_manager_install_description">Для обзора файлов установите OpenIntents File Manager</string>
<string name="cancel">Отмена</string>
<string name="clipboard_cleared">Буфер обмена очищен</string>
<string name="clipboard_error_title">Ошибка буфера обмена</string>
<string name="clipboard_error">Некоторые устройства Samsung не дают приложению использовать буфер обмена.</string>
@@ -77,7 +76,6 @@
<string name="error_wrong_length">Поле \"Длина\" должно быть положительным целым числом.</string>
<string name="field_name">Название поля</string>
<string name="field_value">Значение поля</string>
<string name="file_not_found">Файл не найден.</string>
<string name="file_not_found_content">Файл не найден. Попробуйте повторно открыть через встроенный поставщик содержимого.</string>
<string name="file_browser">Обзор файлов</string>
<string name="generate_password">Генерация пароля</string>

View File

@@ -30,7 +30,6 @@
<string name="menu_app_settings">Nastavenia aplikácie</string>
<string name="brackets">Konzoly</string>
<string name="file_manager_install_description">Prezeranie súborov vyžaduje otvorenie Správcu súborov, kliknite nižšie pre inštalovanie. Kôli chybám v správcovi súborov, prehľadávanie nemusí pracovať správne, ak prehľadávate prvý krát.</string>
<string name="cancel">Zrušiť</string>
<string name="clipboard_cleared">Schránka vyčistená.</string>
<string name="clipboard_timeout">Timeout Schránky</string>
<string name="clipboard_timeout_summary">Čas uchovania v schránke</string>
@@ -68,7 +67,6 @@
<string name="error_rounds_too_large">Príliš veľa opakovaní. Nastavujem na 2147483648.</string>
<string name="error_title_required">Vyžaduje sa názov.</string>
<string name="error_wrong_length">Zadajte celé kladné číslo na dĺžku poľa</string>
<string name="file_not_found">Súbor nenájdený.</string>
<string name="file_browser">Správca Súborov</string>
<string name="generate_password">Generovať Heslo</string>
<string name="hint_conf_pass">potvrdiť heslo</string>

View File

@@ -33,7 +33,6 @@
<string name="beta_dontask">Visa inte igen</string>
<string name="brackets">Parenteser</string>
<string name="file_manager_install_description">Filhantering kräver Open Intents File Manager, klicka nedan för att installera. Filhanteraren kanske inte fungerar korrekt vid första användningen.</string>
<string name="cancel">Avbryt</string>
<string name="clipboard_cleared">Urklippet är rensat.</string>
<string name="clipboard_error_title">Urklippsfel</string>
<string name="clipboard_error">Vissa Samsung-telefoner har en bugg som gör att applikationer inte kan kopiera till urklipp. För mer detaljer, gå till:</string>
@@ -77,7 +76,6 @@
<string name="error_wrong_length">Ange ett positivt heltal i fältet för längd</string>
<string name="field_name">Fältnamn</string>
<string name="field_value">Fältvärde</string>
<string name="file_not_found">Filen hittades inte.</string>
<string name="file_browser">Filhanterare</string>
<string name="generate_password">Generera lösenord</string>
<string name="hint_conf_pass">bekräfta lösenord</string>

View File

@@ -35,7 +35,6 @@
<string name="brackets">Parantez</string>
<string name="extended_ASCII">Genişletilmiş ASCII</string>
<string name="file_manager_install_description">OpenIntents Dosya Yöneticisi\'ni yükleyerek dosyalara göz atın</string>
<string name="cancel">İptal</string>
<string name="allow">İzin ver</string>
<string name="clipboard_cleared">Pano temizlendi</string>
<string name="clipboard_error_title">Pano hatası</string>
@@ -85,7 +84,6 @@
<string name="error_move_folder_in_itself">Bir grubu kendine taşıyamazsın.</string>
<string name="field_name">Alan adı</string>
<string name="field_value">Alan değeri</string>
<string name="file_not_found">Dosya bulunamadı.</string>
<string name="file_not_found_content">Dosya bulunamadı. Dosya tarayıcınızda yeniden açmayı deneyin.</string>
<string name="file_browser">Dosya tarayıcı</string>
<string name="generate_password">Parola üret</string>

View File

@@ -31,7 +31,6 @@
<string name="menu_app_settings">Налаштування програми</string>
<string name="brackets">Дужки</string>
<string name="file_manager_install_description">Для перегляду файла необхідно Open Intents File Manager, натисніть нижче для його інсталяції. У зв’язку з деякими недоробками у менеджері файлів перегляд може працювати некоректно при запуску перший раз.</string>
<string name="cancel">Відміна</string>
<string name="clipboard_cleared">Буфер обміну очищено.</string>
<string name="clipboard_timeout">Тайм-аут буфера обміну</string>
<string name="clipboard_timeout_summary">Час через який буде очищено буфер обміну після копіювання ім’я користувача чи пароля</string>
@@ -69,7 +68,6 @@
<string name="error_rounds_too_large">Надто багато циклів. Установлено 2147483648.</string>
<string name="error_title_required">Необхідно вказати заголовок.</string>
<string name="error_wrong_length">Введіть ціле число на усю довжину поля</string>
<string name="file_not_found">Файл не знайдено.</string>
<string name="file_browser">Перегляд файлів</string>
<string name="generate_password">Згенерувати пароль</string>
<string name="hint_conf_pass">підтвердження пароля</string>

View File

@@ -31,7 +31,6 @@
<string name="menu_app_settings">应用设置</string>
<string name="brackets">括号</string>
<string name="file_manager_install_description">安装 OpenIntents File Manager 应用来选取文件</string>
<string name="cancel">取消</string>
<string name="clipboard_cleared">剪贴板已清空</string>
<string name="clipboard_timeout">剪贴板清空延时</string>
<string name="clipboard_timeout_summary">剪贴板保存时间</string>
@@ -69,7 +68,6 @@
<string name="error_rounds_too_large">“变换次数”过多。已设置为 2147483648。</string>
<string name="error_title_required">请添加标题。</string>
<string name="error_wrong_length">请在“长度”字段输入一个正整数。</string>
<string name="file_not_found">找不到文件。</string>
<string name="file_browser">文件浏览器</string>
<string name="generate_password">生成密码</string>
<string name="hint_conf_pass">确认密码</string>

View File

@@ -30,7 +30,6 @@
<string name="menu_app_settings">應用程式設定</string>
<string name="brackets">括弧</string>
<string name="file_manager_install_description">流覽檔需要安裝Open Intents File Manager軟體點下面安裝。由於一些檔管理軟體的差異在你第一次瀏覽時可能無法正常工作。</string>
<string name="cancel">取消</string>
<string name="clipboard_cleared">剪貼板已清除</string>
<string name="clipboard_timeout">剪貼板超時</string>
<string name="clipboard_timeout_summary">複製用戶名或密碼到剪貼板後清除的時間</string>
@@ -68,7 +67,6 @@
<string name="error_rounds_too_large">次數太多。最大設置到2147483648。</string>
<string name="error_title_required">標題為必填。</string>
<string name="error_wrong_length">長度欄位輸入一個正整數</string>
<string name="file_not_found">文件未找到。</string>
<string name="file_browser">檔案管理器</string>
<string name="generate_password">生成密碼</string>
<string name="hint_conf_pass">確認密碼</string>

View File

@@ -148,16 +148,20 @@
<string name="settings_database_security_key" translatable="false">settings_database_security_key</string>
<string name="settings_database_credentials_key" translatable="false">settings_database_credentials_key</string>
<string name="database_general_key" translatable="false">database_general_key</string>
<string name="database_category_general_key" translatable="false">database_category_general_key</string>
<string name="database_name_key" translatable="false">database_name_key</string>
<string name="database_description_key" translatable="false">database_description_key</string>
<string name="database_default_username_key" translatable="false">database_default_username_key</string>
<string name="database_custom_color_key" translatable="false">database_custom_color_key</string>
<string name="database_version_key" translatable="false">database_version_key</string>
<string name="database_history_key" translatable="false">database_history_key</string>
<string name="database_category_compression_key" translatable="false">database_category_compression_key</string>
<string name="database_data_compression_key" translatable="false">database_data_compression_key</string>
<string name="database_category_recycle_bin_key" translatable="false">database_category_recycle_bin_key</string>
<string name="recycle_bin_key" translatable="false">recycle_bin_key</string>
<string name="database_category_history_key" translatable="false">database_category_history_key</string>
<string name="max_history_items_key" translatable="false">max_history_items_key</string>
<string name="max_history_size_key" translatable="false">max_history_size_key</string>

View File

@@ -37,7 +37,6 @@
<string name="brackets">Brackets</string>
<string name="extended_ASCII">Extended ASCII</string>
<string name="file_manager_install_description">Create, Open and Save a database file requires installing a file manager that accepts the Intent action ACTION_CREATE_DOCUMENT and ACTION_OPEN_DOCUMENT</string>
<string name="cancel">Cancel</string>
<string name="allow">Allow</string>
<string name="clipboard_cleared">Clipboard cleared</string>
<string name="clipboard_error_title">Clipboard error</string>
@@ -101,6 +100,7 @@
<string name="error_load_database">Could not load your database.</string>
<string name="error_load_database_KDF_memory">Could not load the key. Try to lower the KDF \"Memory Usage\".</string>
<string name="error_pass_gen_type">At least one password generation type must be selected.</string>
<string name="error_disallow_no_credentials">At lest one credential must be set.</string>
<string name="error_pass_match">The passwords do not match.</string>
<string name="error_rounds_too_large">\"Transformation rounds\" too high. Setting to 2147483648.</string>
<string name="error_string_key">Each string must have a field name.</string>
@@ -113,7 +113,6 @@
<string name="error_create_database_file">Unable to create database with this password and key file.</string>
<string name="field_name">Field name</string>
<string name="field_value">Field value</string>
<string name="file_not_found">Could not find file.</string>
<string name="file_not_found_content">Could not find file. Try reopening it from your file browser.</string>
<string name="file_browser">File browser</string>
<string name="generate_password">Generate password</string>
@@ -128,6 +127,7 @@
<string name="install_from_play_store">Install from Play Store</string>
<string name="invalid_password">Could not read password or keyfile.</string>
<string name="invalid_algorithm">Wrong algorithm.</string>
<string name="invalid_db_same_uuid">%1$s with the same UUID %2$s already exists.</string>
<string name="invalid_db_sig">Could not recognize the database format.</string>
<string name="keyfile_does_not_exist">No keyfile exists.</string>
<string name="keyfile_is_empty">The keyfile is empty.</string>
@@ -183,6 +183,8 @@
<string name="read_only">Write-protected</string>
<string name="read_only_warning">KeePass DX needs write permission in order to change anything in your database.</string>
<string name="read_only_kitkat_warning">Starting with Android KitKat, some devices no longer allow apps to write to the SD card.</string>
<string name="contains_duplicate_uuid">The database contains duplicate UUIDs.</string>
<string name="contains_duplicate_uuid_procedure">By validating this dialog, KeePass DX will fix the problem (by generating new UUIDs for duplicates) and continue.</string>
<string name="selection_mode">Selection mode</string>
<string name="recentfile_title">Recent file history</string>
<string name="recentfile_summary">Remember recent filenames</string>

View File

@@ -21,7 +21,7 @@
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="@string/database_general_key"
android:key="@string/database_category_general_key"
android:title="@string/general">
<com.kunzisoft.keepass.settings.preference.InputTextPreference
@@ -36,20 +36,21 @@
android:title="@string/database_description_title"
android:positiveButtonText="@string/entry_save"
android:negativeButtonText="@string/entry_cancel"/>
<!-- TODO username preference -->
<com.kunzisoft.keepass.settings.preference.InputTextPreference
android:key="@string/database_default_username_key"
android:persistent="false"
android:title="@string/database_default_username_title"
android:enabled="false"
android:positiveButtonText="@string/entry_save"
android:negativeButtonText="@string/entry_cancel"/>
<!-- TODO custom color preference -->
<Preference
<com.kunzisoft.keepass.settings.preference.DialogColorPreference
xmlns:chroma="http://schemas.android.com/apk/res-auto"
android:key="@string/database_custom_color_key"
android:persistent="false"
android:enabled="false"
android:title="@string/database_custom_color_title"/>
android:title="@string/database_custom_color_title"
android:summary="[color]"
chroma:chromaShapePreview="ROUNDED_SQUARE"
chroma:chromaColorMode="RGB"
chroma:chromaIndicatorMode="HEX" />
<Preference
android:key="@string/database_version_key"
android:persistent="false"
@@ -58,6 +59,7 @@
</PreferenceCategory>
<PreferenceCategory
android:key="@string/database_category_compression_key"
android:title="@string/compression">
<com.kunzisoft.keepass.settings.preference.DialogListExplanationPreference
@@ -68,6 +70,7 @@
</PreferenceCategory>
<PreferenceCategory
android:key="@string/database_category_recycle_bin_key"
android:title="@string/recycle_bin">
<SwitchPreference
@@ -80,7 +83,7 @@
</PreferenceCategory>
<PreferenceCategory
android:key="@string/database_history_key"
android:key="@string/database_category_history_key"
android:title="@string/entry_history">
<com.kunzisoft.keepass.settings.preference.InputNumberPreference

Some files were not shown because too many files have changed in this diff Show More