mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'develop' into feature/OPEN_DOCUMENT
This commit is contained in:
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
|
||||
dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
val bundle = Bundle()
|
||||
mListener?.cancelPassword(bundle)
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
48
app/src/main/res/layout/pref_dialog_input_color.xml
Normal file
48
app/src/main/res/layout/pref_dialog_input_color.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 d’activer 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user