diff --git a/CHANGELOG b/CHANGELOG
index 2b3c7695c..4725edc0c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,16 @@
+KeePassDX(2.9.9)
+ * Detect file changes and reload database #794
+
+KeePassDX(2.9.8)
+ * Fix specific attachments with kdbx3.1 databases #828
+ * Fix small bugs
+
+KeePassDX(2.9.7)
+ * Remove write permission since Android 10 #823
+ * Fix small bugs
+
KeePassDX(2.9.6)
- *
+ * Fix KeyFile bug #820
KeePassDX(2.9.5)
* Unlock database by device credentials (PIN/Password/Pattern) with Android M+ #102 #152 #811
diff --git a/app/build.gradle b/app/build.gradle
index a2feb0d71..847c6927f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 30
- versionCode = 50
- versionName = "2.9.6"
+ versionCode = 53
+ versionName = "2.9.9"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -92,7 +92,7 @@ android {
}
}
-def room_version = "2.2.5"
+def room_version = "2.2.6"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 716adef98..4cdd71c4b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,10 +14,15 @@
android:name="android.permission.USE_BIOMETRIC" />
+
+ android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="28"
+ tools:ignore="ScopedStorage" />
+
+ android:name="android.permission.QUERY_ALL_PACKAGES"
+ tools:ignore="QueryAllPackagesPermission" />
{
+ // Close the current activity
+ finish()
+ }
}
coordinatorLayout?.showActionError(result)
}
@@ -408,6 +414,9 @@ class EntryActivity : LockingActivity() {
menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false
}
+ if (mSpecialMode != SpecialMode.DEFAULT) {
+ menu.findItem(R.id.menu_reload_database)?.isVisible = false
+ }
val gotoUrl = menu.findItem(R.id.menu_goto_url)
gotoUrl?.apply {
@@ -501,6 +510,9 @@ class EntryActivity : LockingActivity() {
R.id.menu_save_database -> {
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
}
+ R.id.menu_reload_database -> {
+ mProgressDatabaseTaskProvider?.startDatabaseReload(false)
+ }
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item)
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
index 4cc92c781..54a8bfa73 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
@@ -61,6 +61,7 @@ import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
+import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement
@@ -335,6 +336,10 @@ class EntryEditActivity : LockingActivity(),
Log.e(TAG, "Unable to retrieve entry after database action", e)
}
}
+ ACTION_DATABASE_RELOAD_TASK -> {
+ // Close the current activity
+ finish()
+ }
}
coordinatorLayout?.showActionError(result)
}
@@ -610,13 +615,7 @@ class EntryEditActivity : LockingActivity(),
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
-
- val inflater = menuInflater
- inflater.inflate(R.menu.database, menu)
- // Save database not needed here
- menu.findItem(R.id.menu_save_database)?.isVisible = false
- MenuUtil.contributionMenuInflater(inflater, menu)
-
+ MenuUtil.contributionMenuInflater(menuInflater, menu)
return true
}
@@ -673,9 +672,6 @@ class EntryEditActivity : LockingActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
- R.id.menu_save_database -> {
- mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
- }
R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
index 4e4c48cb4..4b230d848 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
@@ -70,6 +70,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
+import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
@@ -317,7 +318,12 @@ class GroupActivity : LockingActivity(),
if (result.isSuccess) {
// Rebuild all the list to avoid bug when delete node from sort
- mListNodesFragment?.rebuildList()
+ try {
+ mListNodesFragment?.rebuildList()
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to rebuild the list after deletion")
+ e.printStackTrace()
+ }
// Add trash in views list if it doesn't exists
if (database.isRecycleBinEnabled) {
@@ -337,6 +343,12 @@ class GroupActivity : LockingActivity(),
}
}
}
+ ACTION_DATABASE_RELOAD_TASK -> {
+ // Reload the current activity
+ startActivity(intent)
+ finish()
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
+ }
}
coordinatorLayout?.showActionError(result)
@@ -873,6 +885,8 @@ class GroupActivity : LockingActivity(),
}
if (mSpecialMode == SpecialMode.DEFAULT) {
MenuUtil.defaultMenuInflater(inflater, menu)
+ } else {
+ menu.findItem(R.id.menu_reload_database)?.isVisible = false
}
// Menu for recycle bin
@@ -998,6 +1012,10 @@ class GroupActivity : LockingActivity(),
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
return true
}
+ R.id.menu_reload_database -> {
+ mProgressDatabaseTaskProvider?.startDatabaseReload(false)
+ return true
+ }
R.id.menu_empty_recycle_bin -> {
mCurrentGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements
@@ -1125,7 +1143,16 @@ class GroupActivity : LockingActivity(),
private fun rebuildListNodes() {
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment?
// to refresh fragment
- mListNodesFragment?.rebuildList()
+ try {
+ mListNodesFragment?.rebuildList()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ coordinatorLayout?.let { coordinatorLayout ->
+ Snackbar.make(coordinatorLayout,
+ R.string.error_rebuild_list,
+ Snackbar.LENGTH_LONG).asError().show()
+ }
+ }
mCurrentGroup = mListNodesFragment?.mainGroup
// Remove search in intent
deletePreviousSearchGroup()
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt
index e5657f5cc..63deba4ef 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt
@@ -22,30 +22,24 @@ package com.kunzisoft.keepass.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.util.Log
+import android.view.*
+import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.view.ActionMode
-
import com.kunzisoft.keepass.R
-import com.kunzisoft.keepass.adapters.NodeAdapter
-import com.kunzisoft.keepass.database.element.SortNodeEnum
-import com.kunzisoft.keepass.database.element.Group
-import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
-import com.kunzisoft.keepass.settings.PreferencesUtil
-import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
+import com.kunzisoft.keepass.activities.stylish.StylishFragment
+import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.database.element.Group
+import com.kunzisoft.keepass.database.element.SortNodeEnum
+import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
+import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
@@ -197,7 +191,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Refresh data
- rebuildList()
+ try {
+ rebuildList()
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to rebuild the list during resume")
+ e.printStackTrace()
+ }
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found "
@@ -209,10 +208,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
+ @Throws(IllegalArgumentException::class)
fun rebuildList() {
// Add elements to the list
mainGroup?.let { mainGroup ->
mAdapter?.apply {
+ // Thrown an exception when sort cannot be performed
rebuildList(mainGroup)
// To visually change the elements
if (PreferencesUtil.APPEARANCE_CHANGED) {
@@ -231,8 +232,13 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Tell the adapter to refresh it's list
- mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
- rebuildList()
+ try {
+ mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
+ rebuildList()
+ } catch (e:Exception) {
+ Log.e(TAG, "Unable to rebuild the list with the sort")
+ e.printStackTrace()
+ }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
index 5f0aaa811..10372b4cc 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
@@ -588,13 +588,13 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
// Check permission
private fun checkPermission() {
- val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
- val permissions = arrayOf(writePermission)
- if (Build.VERSION.SDK_INT >= 23
+ if (Build.VERSION.SDK_INT in 23..28
&& !readOnly
&& !mPermissionAsked) {
mPermissionAsked = true
// Check self permission to show or not the dialog
+ val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+ val permissions = arrayOf(writePermission)
if (toolbar != null
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
@@ -720,7 +720,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews()
- Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
+ Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
}
Activity.RESULT_CANCELED -> {
clearCredentialsViews()
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseChangedDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseChangedDialogFragment.kt
new file mode 100644
index 000000000..6a83f0df7
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseChangedDialogFragment.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2020 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.activities.dialogs
+
+import android.app.Dialog
+import android.os.Bundle
+import android.text.SpannableStringBuilder
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
+
+
+class DatabaseChangedDialogFragment : DialogFragment() {
+
+ var actionDatabaseListener: ActionDatabaseChangedListener? = null
+
+ override fun onPause() {
+ super.onPause()
+ actionDatabaseListener = null
+ this.dismiss()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ activity?.let { activity ->
+
+ val oldSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(OLD_FILE_DATABASE_INFO)
+ val newSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(NEW_FILE_DATABASE_INFO)
+
+ if (oldSnapFileDatabaseInfo != null && newSnapFileDatabaseInfo != null) {
+ // Use the Builder class for convenient dialog construction
+ val builder = AlertDialog.Builder(activity)
+
+ val stringBuilder = SpannableStringBuilder()
+ if (newSnapFileDatabaseInfo.exists) {
+ stringBuilder.append(getString(R.string.warning_database_info_changed))
+ stringBuilder.append("\n\n" +oldSnapFileDatabaseInfo.toString(activity)
+ + "\n→\n" +
+ newSnapFileDatabaseInfo.toString(activity) + "\n\n")
+ stringBuilder.append(getString(R.string.warning_database_info_changed_options))
+ } else {
+ stringBuilder.append(getString(R.string.warning_database_revoked))
+ }
+ builder.setMessage(stringBuilder)
+ builder.setPositiveButton(android.R.string.ok) { _, _ ->
+ actionDatabaseListener?.validateDatabaseChanged()
+ }
+ return builder.create()
+ }
+ }
+ return super.onCreateDialog(savedInstanceState)
+ }
+
+ interface ActionDatabaseChangedListener {
+ fun validateDatabaseChanged()
+ }
+
+ companion object {
+
+ const val DATABASE_CHANGED_DIALOG_TAG = "databaseChangedDialogFragment"
+ private const val OLD_FILE_DATABASE_INFO = "OLD_FILE_DATABASE_INFO"
+ private const val NEW_FILE_DATABASE_INFO = "NEW_FILE_DATABASE_INFO"
+
+ fun getInstance(oldSnapFileDatabaseInfo: SnapFileDatabaseInfo,
+ newSnapFileDatabaseInfo: SnapFileDatabaseInfo)
+ : DatabaseChangedDialogFragment {
+ val fragment = DatabaseChangedDialogFragment()
+ fragment.arguments = Bundle().apply {
+ putParcelable(OLD_FILE_DATABASE_INFO, oldSnapFileDatabaseInfo)
+ putParcelable(NEW_FILE_DATABASE_INFO, newSnapFileDatabaseInfo)
+ }
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt
index 91c8e5b2f..bc9ce9365 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt
@@ -18,7 +18,7 @@ import com.kunzisoft.keepass.view.SpecialModeView
abstract class SpecialModeActivity : StylishActivity() {
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
- protected var mTypeMode: TypeMode = TypeMode.DEFAULT
+ private var mTypeMode: TypeMode = TypeMode.DEFAULT
private var mSpecialModeView: SpecialModeView? = null
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/App.kt b/app/src/main/java/com/kunzisoft/keepass/app/App.kt
index 8094648b6..5207b1e51 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/App.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/App.kt
@@ -34,7 +34,7 @@ class App : MultiDexApplication() {
}
override fun onTerminate() {
- Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
+ Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
super.onTerminate()
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt
index 6b8c22781..9c7242fb9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt
@@ -47,7 +47,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""),
fileDatabaseInfo.exists,
- fileDatabaseInfo.getModificationString(),
+ fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
},
@@ -90,7 +90,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
fileDatabaseInfo.exists,
- fileDatabaseInfo.getModificationString(),
+ fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
)
@@ -152,7 +152,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistory.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists,
- fileDatabaseInfo.getModificationString(),
+ fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
index b00528d54..6ad976420 100644
--- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
@@ -117,10 +117,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// To wait resume
- activityResult = ActivityResult(requestCode, resultCode, data)
+ if (keepConnection) {
+ activityResult = ActivityResult(requestCode, resultCode, data)
+ }
keepConnection = false
-
- super.onActivityResult(requestCode, resultCode, data)
}
override fun onResume() {
@@ -237,7 +237,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
Mode.WAIT_CREDENTIAL
})
}
- } ?: throw IODatabaseException()
+ }
}
}
}
@@ -304,7 +304,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M)
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
if (allowOpenBiometricPrompt) {
if (cryptoPrompt.isDeviceCredentialOperation)
keepConnection = true
@@ -329,7 +329,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
openAdvancedUnlockPrompt(cryptoPrompt)
}
- } ?: throw Exception("AdvancedUnlockHelper not initialized")
+ } ?: throw Exception("AdvancedUnlockManager not initialized")
}
@RequiresApi(Build.VERSION_CODES.M)
@@ -358,21 +358,25 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
} ?: deleteEncryptedDatabaseKey()
}
} ?: throw IODatabaseException()
- } ?: throw Exception("AdvancedUnlockHelper not initialized")
+ } ?: throw Exception("AdvancedUnlockManager not initialized")
}
@Synchronized
fun initAdvancedUnlockMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mAllowAdvancedUnlockMenu = false
- when (biometricMode) {
- Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
- Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
- Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
- Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
- Mode.WAIT_CREDENTIAL -> initWaitData()
- Mode.STORE_CREDENTIAL -> initEncryptData()
- Mode.EXTRACT_CREDENTIAL -> initDecryptData()
+ try {
+ when (biometricMode) {
+ Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
+ Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
+ Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
+ Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
+ Mode.WAIT_CREDENTIAL -> initWaitData()
+ Mode.STORE_CREDENTIAL -> initEncryptData()
+ Mode.EXTRACT_CREDENTIAL -> initDecryptData()
+ }
+ } catch (e: Exception) {
+ onGenericException(e)
}
invalidateBiometricMenu()
}
@@ -388,7 +392,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
mAddBiometricMenuInProgress = false
- requireActivity().invalidateOptionsMenu()
+ activity?.invalidateOptionsMenu()
}
}
}
@@ -442,7 +446,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedMessageView(errString.toString())
}
@@ -450,7 +454,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationFailed() {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
}
@@ -458,7 +462,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationSucceeded() {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> {
}
@@ -485,7 +489,9 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
advancedUnlockManager?.decryptData(value)
} ?: deleteEncryptedDatabaseKey()
}
- } ?: throw IODatabaseException()
+ } ?: run {
+ onAuthenticationError(-1, getString(R.string.error_database_uri_null))
+ }
}
}
}
@@ -515,7 +521,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
private fun showViews(show: Boolean) {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
mAdvancedUnlockInfoView?.visibility = if (show)
View.VISIBLE
else {
@@ -526,20 +532,20 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedTitleView(textId: Int) {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
mAdvancedUnlockInfoView?.setTitle(textId)
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedMessageView(textId: Int) {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
mAdvancedUnlockInfoView?.setMessage(textId)
}
}
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
- requireActivity().runOnUiThread {
+ activity?.runOnUiThread {
mAdvancedUnlockInfoView?.message = text
}
}
@@ -548,18 +554,20 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
readOnlyEducationPerformed: Boolean,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
- && !readOnlyEducationPerformed) {
- val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
- PreferencesUtil.isAdvancedUnlockEnable(requireContext())
- && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
- || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
- && mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
- && mAdvancedUnlockInfoView?.unlockIconImageView != null
- && passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
- onEducationViewClick,
- onOuterViewClick)
- }
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && !readOnlyEducationPerformed) {
+ val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
+ PreferencesUtil.isAdvancedUnlockEnable(requireContext())
+ && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
+ || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
+ && mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
+ && mAdvancedUnlockInfoView?.unlockIconImageView != null
+ && passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
+ onEducationViewClick,
+ onOuterViewClick)
+ }
+ } catch (ignored: Exception) {}
}
enum class Mode {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
index 7b7f90d0b..f2763f30a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
@@ -26,7 +26,6 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil
-import com.kunzisoft.keepass.utils.closeDatabase
class CreateDatabaseRunnable(context: Context,
private val mDatabase: Database,
@@ -47,7 +46,7 @@ class CreateDatabaseRunnable(context: Context,
createData(mDatabaseUri, databaseName, rootName)
}
} catch (e: Exception) {
- mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
setError(e)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
index 5f9d0ce04..f732f3779 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
@@ -31,7 +31,6 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtil
-import com.kunzisoft.keepass.utils.closeDatabase
class LoadDatabaseRunnable(private val context: Context,
private val mDatabase: Database,
@@ -47,7 +46,7 @@ class LoadDatabaseRunnable(private val context: Context,
override fun onStartRun() {
// Clear before we load
- mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
}
override fun onActionRun() {
@@ -59,9 +58,6 @@ class LoadDatabaseRunnable(private val context: Context,
mFixDuplicateUUID,
progressTaskUpdater)
}
- catch (e: DuplicateUuidDatabaseException) {
- setError(e)
- }
catch (e: LoadDatabaseException) {
setError(e)
}
@@ -83,7 +79,7 @@ class LoadDatabaseRunnable(private val context: Context,
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
} else {
- mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt
index 279b40e27..534067403 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt
@@ -26,6 +26,8 @@ import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import androidx.fragment.app.FragmentActivity
+import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
+import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.Entry
@@ -35,6 +37,7 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
+import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
@@ -44,6 +47,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
+import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
@@ -84,6 +88,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
private var serviceConnection: ServiceConnection? = null
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
+ private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
@@ -101,6 +106,28 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
}
}
+ private val mActionDatabaseListener = object: DatabaseChangedDialogFragment.ActionDatabaseChangedListener {
+ override fun validateDatabaseChanged() {
+ mBinder?.getService()?.saveDatabaseInfo()
+ }
+ }
+
+ private var databaseInfoListener = object: DatabaseTaskNotificationService.DatabaseInfoListener {
+ override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
+ newDatabaseInfo: SnapFileDatabaseInfo) {
+ if (databaseChangedDialogFragment == null) {
+ databaseChangedDialogFragment = activity.supportFragmentManager
+ .findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
+ databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
+ }
+ if (progressTaskDialogFragment == null) {
+ databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(previousDatabaseInfo, newDatabaseInfo)
+ databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
+ databaseChangedDialogFragment?.show(activity.supportFragmentManager, DATABASE_CHANGED_DIALOG_TAG)
+ }
+ }
+ }
+
private fun startDialog(titleId: Int? = null,
messageId: Int? = null,
warningId: Int? = null) {
@@ -140,11 +167,14 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addActionTaskListener(actionTaskListener)
+ addDatabaseFileInfoListener(databaseInfoListener)
getService().checkAction()
+ getService().checkDatabaseInfo()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
+ mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null
}
@@ -206,6 +236,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
fun unregisterProgressTask() {
stopDialog()
+ mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null
@@ -264,6 +295,13 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
, ACTION_DATABASE_LOAD_TASK)
}
+ fun startDatabaseReload(fixDuplicateUuid: Boolean) {
+ start(Bundle().apply {
+ putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
+ }
+ , ACTION_DATABASE_RELOAD_TASK)
+ }
+
fun startDatabaseAssignPassword(databaseUri: Uri,
masterPasswordChecked: Boolean,
masterPassword: String?,
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt
new file mode 100644
index 000000000..8951d3615
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.database.action
+
+import android.content.Context
+import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
+import com.kunzisoft.keepass.database.exception.LoadDatabaseException
+import com.kunzisoft.keepass.settings.PreferencesUtil
+import com.kunzisoft.keepass.tasks.ActionRunnable
+import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
+import com.kunzisoft.keepass.utils.UriUtil
+
+class ReloadDatabaseRunnable(private val context: Context,
+ private val mDatabase: Database,
+ private val progressTaskUpdater: ProgressTaskUpdater?,
+ private val mLoadDatabaseResult: ((Result) -> Unit)?)
+ : ActionRunnable() {
+
+ override fun onStartRun() {
+ // Clear before we load
+ mDatabase.clear(UriUtil.getBinaryDir(context))
+ }
+
+ override fun onActionRun() {
+ try {
+ mDatabase.reloadData(context.contentResolver,
+ UriUtil.getBinaryDir(context),
+ progressTaskUpdater)
+ }
+ catch (e: LoadDatabaseException) {
+ setError(e)
+ }
+
+ if (result.isSuccess) {
+ // Register the current time to init the lock timer
+ PreferencesUtil.saveCurrentTime(context)
+ } else {
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
+ }
+ }
+
+ override fun onFinishRun() {
+ mLoadDatabaseResult?.invoke(result)
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
index 444b86ba6..d0da8c171 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
@@ -31,10 +31,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
-import com.kunzisoft.keepass.database.exception.DatabaseOutputException
-import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
-import com.kunzisoft.keepass.database.exception.LoadDatabaseException
-import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
+import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
@@ -330,29 +327,11 @@ class Database {
}
@Throws(LoadDatabaseException::class)
- fun loadData(uri: Uri, password: String?, keyfile: Uri?,
- readOnly: Boolean,
- contentResolver: ContentResolver,
- cacheDirectory: File,
- fixDuplicateUUID: Boolean,
- progressTaskUpdater: ProgressTaskUpdater?) {
-
- this.fileUri = uri
- isReadOnly = readOnly
- if (uri.scheme == "file") {
- val file = File(uri.path!!)
- isReadOnly = !file.canWrite()
- }
-
- // Pass KeyFile Uri as InputStreams
+ private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
+ openDatabaseKDB: (InputStream) -> DatabaseKDB,
+ openDatabaseKDBX: (InputStream) -> DatabaseKDBX) {
var databaseInputStream: InputStream? = null
- var keyFileInputStream: InputStream? = null
try {
- // Get keyFile inputStream
- keyfile?.let {
- keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
- }
-
// Load Data, pass Uris as InputStreams
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
?: throw IOException("Database input stream cannot be retrieve")
@@ -374,22 +353,10 @@ class Database {
when {
// Header of database KDB
- DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB(
- cacheDirectory,
- fixDuplicateUUID)
- .openDatabase(databaseInputStream,
- password,
- keyFileInputStream,
- progressTaskUpdater))
+ DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(openDatabaseKDB(databaseInputStream))
// Header of database KDBX
- DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX(
- cacheDirectory,
- fixDuplicateUUID)
- .openDatabase(databaseInputStream,
- password,
- keyFileInputStream,
- progressTaskUpdater))
+ DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(openDatabaseKDBX(databaseInputStream))
// Header not recognized
else -> throw SignatureDatabaseException()
@@ -397,15 +364,87 @@ class Database {
this.mSearchHelper = SearchHelper()
loaded = true
-
} catch (e: LoadDatabaseException) {
throw e
+ } finally {
+ databaseInputStream?.close()
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ fun loadData(uri: Uri, password: String?, keyfile: Uri?,
+ readOnly: Boolean,
+ contentResolver: ContentResolver,
+ cacheDirectory: File,
+ fixDuplicateUUID: Boolean,
+ progressTaskUpdater: ProgressTaskUpdater?) {
+
+ // Save database URI
+ this.fileUri = uri
+
+ // Check if the file is writable
+ this.isReadOnly = readOnly
+ if (uri.scheme == "file") {
+ val file = File(uri.path!!)
+ isReadOnly = !file.canWrite()
+ }
+
+ // Pass KeyFile Uri as InputStreams
+ var keyFileInputStream: InputStream? = null
+ try {
+ // Get keyFile inputStream
+ keyfile?.let {
+ keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
+ }
} catch (e: Exception) {
throw FileNotFoundDatabaseException()
} finally {
keyFileInputStream?.close()
- databaseInputStream?.close()
}
+
+ // Read database stream for the first time
+ readDatabaseStream(contentResolver, uri,
+ { databaseInputStream ->
+ DatabaseInputKDB(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ password,
+ keyFileInputStream,
+ progressTaskUpdater,
+ fixDuplicateUUID)
+ },
+ { databaseInputStream ->
+ DatabaseInputKDBX(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ password,
+ keyFileInputStream,
+ progressTaskUpdater,
+ fixDuplicateUUID)
+ }
+ )
+ }
+
+ @Throws(LoadDatabaseException::class)
+ fun reloadData(contentResolver: ContentResolver,
+ cacheDirectory: File,
+ progressTaskUpdater: ProgressTaskUpdater?) {
+
+ // Retrieve the stream from the old database URI
+ fileUri?.let { oldDatabaseUri ->
+ readDatabaseStream(contentResolver, oldDatabaseUri,
+ { databaseInputStream ->
+ DatabaseInputKDB(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ masterKey,
+ progressTaskUpdater)
+ },
+ { databaseInputStream ->
+ DatabaseInputKDBX(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ masterKey,
+ progressTaskUpdater)
+ }
+ )
+ } ?: throw IODatabaseException()
}
fun isGroupSearchable(group: Group, omitBackup: Boolean): Boolean {
@@ -531,7 +570,7 @@ class Database {
this.fileUri = uri
}
- fun closeAndClear(filesDirectory: File? = null) {
+ fun clear(filesDirectory: File? = null) {
drawFactory.clearCache()
// Delete the cache of the database if present
mDatabaseKDB?.clearCache()
@@ -544,7 +583,10 @@ class Database {
} catch (e: Exception) {
Log.e(TAG, "Unable to clear the directory cache.", e)
}
+ }
+ fun clearAndClose(filesDirectory: File? = null) {
+ clear(filesDirectory)
this.mDatabaseKDB = null
this.mDatabaseKDBX = null
this.fileUri = null
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
index 8da334872..ad819cde5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
@@ -41,6 +41,13 @@ abstract class DatabaseInput>
abstract fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): PwDb
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean = false): PwDb
+
+ @Throws(LoadDatabaseException::class)
+ abstract fun openDatabase(databaseInputStream: InputStream,
+ masterKey: ByteArray,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean = false): PwDb
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
index dd5ac5b6b..f2b67e788 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
@@ -45,8 +45,7 @@ import javax.crypto.spec.SecretKeySpec
/**
* Load a KDB database file.
*/
-class DatabaseInputKDB(cacheDirectory: File,
- private val fixDuplicateUUID: Boolean = false)
+class DatabaseInputKDB(cacheDirectory: File)
: DatabaseInput(cacheDirectory) {
private lateinit var mDatabaseToOpen: DatabaseKDB
@@ -55,7 +54,28 @@ class DatabaseInputKDB(cacheDirectory: File,
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDB {
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDB {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ override fun openDatabase(databaseInputStream: InputStream,
+ masterKey: ByteArray,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDB {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabaseToOpen.masterKey = masterKey
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ private fun openDatabase(databaseInputStream: InputStream,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean,
+ assignMasterKey: (() -> Unit)? = null): DatabaseKDB {
try {
// Load entire file, most of it's encrypted.
@@ -84,7 +104,7 @@ class DatabaseInputKDB(cacheDirectory: File,
mDatabaseToOpen = DatabaseKDB()
mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID
- mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
+ assignMasterKey?.invoke()
// Select algorithm
when {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
index e9e3fa0b8..c13ba5884 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
@@ -63,8 +63,7 @@ import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import kotlin.math.min
-class DatabaseInputKDBX(cacheDirectory: File,
- private val fixDuplicateUUID: Boolean = false)
+class DatabaseInputKDBX(cacheDirectory: File)
: DatabaseInput(cacheDirectory) {
private var randomStream: StreamCipher? = null
@@ -98,12 +97,30 @@ class DatabaseInputKDBX(cacheDirectory: File,
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDBX {
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDBX {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabase.retrieveMasterKey(password, keyInputStream)
+ }
+ }
+ @Throws(LoadDatabaseException::class)
+ override fun openDatabase(databaseInputStream: InputStream,
+ masterKey: ByteArray,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDBX {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabase.masterKey = masterKey
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ private fun openDatabase(databaseInputStream: InputStream,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean,
+ assignMasterKey: (() -> Unit)? = null): DatabaseKDBX {
try {
- // TODO performance
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
-
mDatabase = DatabaseKDBX()
mDatabase.changeDuplicateId = fixDuplicateUUID
@@ -116,9 +133,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
hashOfHeader = headerAndHash.hash
val pbHeader = headerAndHash.header
- mDatabase.retrieveMasterKey(password, keyInputStream)
+ assignMasterKey?.invoke()
mDatabase.makeFinalKey(header.masterSeed)
- // TODO performance
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
val engine: CipherEngine
@@ -958,7 +974,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
// Create empty binary if not retrieved in pool
if (binaryRetrieve == null) {
binaryRetrieve = mDatabase.buildNewBinary(cacheDirectory,
- compression = false, protection = true, binaryPoolId = id)
+ compression = false, protection = false, binaryPoolId = id)
}
return binaryRetrieve
}
@@ -1024,29 +1040,20 @@ class DatabaseInputKDBX(cacheDirectory: File,
return xpp.safeNextText()
}
- @Throws(XmlPullParserException::class, IOException::class)
- private fun readBase64String(xpp: XmlPullParser): ByteArray {
-
- //readNextNode = false;
- Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
- val plainText = ByteArray(data.size)
- randomStream?.processBytes(data, 0, data.size, plainText, 0)
- return plainText
- }
- return ByteArray(0)
- }
@Throws(XmlPullParserException::class, IOException::class)
private fun readProtectedBase64String(xpp: XmlPullParser): ByteArray? {
- //(xpp.getEventType() == XmlPullParser.START_TAG);
-
if (xpp.attributeCount > 0) {
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) {
- return readBase64String(xpp)
+ Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
+ val plainText = ByteArray(data.size)
+ randomStream?.processBytes(data, 0, data.size, plainText, 0)
+ return plainText
+ }
+ return ByteArray(0)
}
}
-
return null
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
index 422a0fb49..372623f2c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
@@ -38,7 +38,6 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
-import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
@@ -420,41 +419,56 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
}
+ /*
+ // Normally used by a single entry but obsolete because binaries are in meta tag with kdbx3.1-
+ // or in file header with kdbx4
+ // binary.isProtected attribute is not used to create the XML
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
- private fun writeBinary(binary : BinaryAttachment) {
- val binaryLength = binary.length()
- if (binaryLength > 0) {
+ private fun writeEntryBinary(binary : BinaryAttachment) {
+ if (binary.length() > 0) {
if (binary.isProtected) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
-
- binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
- val encoded = ByteArray(buffer.size)
- randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
- val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
- xml.text(charArray, 0, charArray.size)
+ binary.getInputDataStream().use { inputStream ->
+ inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
+ val encoded = ByteArray(buffer.size)
+ randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
+ xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
+ }
}
} else {
- if (binary.isCompressed) {
- xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
- }
// Write the XML
- binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
- val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
- xml.text(charArray, 0, charArray.size)
+ binary.getInputDataStream().use { inputStream ->
+ inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
+ xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
+ }
}
}
}
}
+ */
+ // Only uses with kdbx3.1 to write binaries in meta tag
+ // With kdbx4, don't use this method because binaries are in header file
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
- // Use indexes because necessarily in DatabaseV4 (binary header ref is the order)
+ // Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
- writeBinary(keyBinary.binary)
+ val binary = keyBinary.binary
+ if (binary.length() > 0) {
+ if (binary.isCompressed) {
+ xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
+ }
+ // Write the XML
+ binary.getInputDataStream().use { inputStream ->
+ inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
+ xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
+ }
+ }
+ }
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
}
@@ -523,13 +537,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (protect) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
-
- val data = value.toString().toByteArray(charset("UTF-8"))
- val valLength = data.size
-
- if (valLength > 0) {
- val encoded = ByteArray(valLength)
- randomStream!!.processBytes(data, 0, valLength, encoded, 0)
+ val data = value.toString().toByteArray()
+ val dataLength = data.size
+ if (data.isNotEmpty()) {
+ val encoded = ByteArray(dataLength)
+ randomStream!!.processBytes(data, 0, dataLength, encoded, 0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
}
} else {
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/SnapFileDatabaseInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/SnapFileDatabaseInfo.kt
new file mode 100644
index 000000000..4ef9acb3e
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/model/SnapFileDatabaseInfo.kt
@@ -0,0 +1,62 @@
+package com.kunzisoft.keepass.model
+
+import android.content.Context
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import android.text.format.Formatter
+import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
+import java.text.DateFormat
+import java.util.*
+
+/**
+ * Utility data class to get FileDatabaseInfo at a `t` time
+ */
+data class SnapFileDatabaseInfo(var fileUri: Uri?,
+ var exists: Boolean,
+ var lastModification: Long?,
+ var size: Long?): Parcelable {
+
+ constructor(parcel: Parcel) : this(
+ parcel.readParcelable(Uri::class.java.classLoader),
+ parcel.readByte() != 0.toByte(),
+ parcel.readValue(Long::class.java.classLoader) as? Long,
+ parcel.readValue(Long::class.java.classLoader) as? Long) {
+ }
+
+ fun toString(context: Context): String {
+ val lastModificationString = DateFormat.getDateTimeInstance()
+ .format(Date(lastModification ?: 0))
+ return "$lastModificationString, " +
+ Formatter.formatFileSize(context, size ?: 0)
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(fileUri, flags)
+ parcel.writeByte(if (exists) 1 else 0)
+ parcel.writeValue(lastModification)
+ parcel.writeValue(size)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): SnapFileDatabaseInfo {
+ return SnapFileDatabaseInfo(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+
+ fun fromFileDatabaseInfo(fileDatabaseInfo: FileDatabaseInfo): SnapFileDatabaseInfo {
+ return SnapFileDatabaseInfo(
+ fileDatabaseInfo.fileUri,
+ fileDatabaseInfo.exists,
+ fileDatabaseInfo.getLastModification(),
+ fileDatabaseInfo.getSize())
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt
index bbc0e9bf1..3535fba62 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt
@@ -22,9 +22,8 @@ package com.kunzisoft.keepass.notifications
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
-import android.os.Binder
-import android.os.Bundle
-import android.os.IBinder
+import android.os.*
+import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
@@ -40,6 +39,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
+import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.timeout.TimeoutHelper
@@ -47,6 +47,7 @@ import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import com.kunzisoft.keepass.utils.LOCK_ACTION
import com.kunzisoft.keepass.utils.closeDatabase
+import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
@@ -65,6 +66,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private var mAllowFinishAction = AtomicBoolean()
private var mActionRunning = false
+ private var mSnapFileDatabaseInfo: SnapFileDatabaseInfo? = null
+ private var mDatabaseInfoListeners = LinkedList()
+
private var mIconId: Int = R.drawable.notification_ic_database_load
private var mTitleId: Int = R.string.database_opened
private var mMessageId: Int? = null
@@ -93,6 +97,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
mAllowFinishAction.set(false)
}
}
+
+ fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
+ mDatabaseInfoListeners.add(databaseInfoListener)
+ }
+
+ fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
+ mDatabaseInfoListeners.remove(databaseInfoListener)
+ }
}
interface ActionTaskListener {
@@ -101,6 +113,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun onStopAction(actionTask: String, result: ActionRunnable.Result)
}
+ interface DatabaseInfoListener {
+ fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
+ newDatabaseInfo: SnapFileDatabaseInfo)
+ }
+
/**
* Force to call [ActionTaskListener.onStartAction] if the action is still running
*/
@@ -112,6 +129,31 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
+ fun checkDatabaseInfo() {
+ mDatabase.fileUri?.let {
+ val previousDatabaseInfo = mSnapFileDatabaseInfo
+ val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
+ FileDatabaseInfo(applicationContext, it))
+ if (previousDatabaseInfo != null) {
+ if (previousDatabaseInfo != lastFileDatabaseInfo) {
+ Log.i(TAG, "Database file modified " +
+ "$previousDatabaseInfo != $lastFileDatabaseInfo ")
+ // Call listener to indicate a change in database info
+ mDatabaseInfoListeners.forEach { listener ->
+ listener.onDatabaseInfoChanged(previousDatabaseInfo, lastFileDatabaseInfo)
+ }
+ }
+ }
+ }
+ }
+
+ fun saveDatabaseInfo() {
+ mDatabase.fileUri?.let {
+ mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
+ FileDatabaseInfo(applicationContext, it))
+ }
+ }
+
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return mActionTaskBinder
@@ -138,6 +180,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
val actionRunnable: ActionRunnable? = when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
+ ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask()
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
@@ -193,6 +236,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
} finally {
removeIntentData(intent)
+ // Save the database info after performing action
+ saveDatabaseInfo()
TimeoutHelper.releaseTemporarilyDisableTimeout()
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
if (!mDatabase.loaded) {
@@ -214,7 +259,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
return when (intentAction) {
- ACTION_DATABASE_LOAD_TASK, null -> {
+ ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_RELOAD_TASK,
+ null -> {
START_STICKY
}
else -> {
@@ -248,7 +295,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
else -> {
when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
- ACTION_DATABASE_LOAD_TASK -> R.string.loading_database
+ ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_RELOAD_TASK -> R.string.loading_database
ACTION_DATABASE_SAVE -> R.string.saving_database
else -> {
R.string.command_execution
@@ -258,13 +306,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
mMessageId = when (intentAction) {
- ACTION_DATABASE_LOAD_TASK -> null
+ ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_RELOAD_TASK -> null
else -> null
}
mWarningId =
if (!saveAction
- || intentAction == ACTION_DATABASE_LOAD_TASK)
+ || intentAction == ACTION_DATABASE_LOAD_TASK
+ || intentAction == ACTION_DATABASE_RELOAD_TASK)
null
else
R.string.do_not_kill_app
@@ -342,9 +392,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
* Execute action with a coroutine
*/
private suspend fun executeAction(progressTaskUpdater: ProgressTaskUpdater,
- onPreExecute: () -> Unit,
- onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
- onPostExecute: (result: ActionRunnable.Result) -> Unit) {
+ onPreExecute: () -> Unit,
+ onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
+ onPostExecute: (result: ActionRunnable.Result) -> Unit) {
mAllowFinishAction.set(false)
TimeoutHelper.temporarilyDisableTimeout()
@@ -465,6 +515,17 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
+ private fun buildDatabaseReloadActionTask(): ActionRunnable {
+ return ReloadDatabaseRunnable(
+ this,
+ mDatabase,
+ this
+ ) { result ->
+ // No need to add each info to reload database
+ result.data = Bundle()
+ }
+ }
+
private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(DATABASE_URI_KEY)
&& intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY)
@@ -770,6 +831,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK"
+ const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK"
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt
index d03cd3411..b6b0acb7f 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt
@@ -103,6 +103,6 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
}
interface Callback {
- fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen)
+ fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean = false)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
index de0bbadda..7fedcd403 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
@@ -552,6 +552,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
true
}
+ R.id.menu_reload_database -> {
+ settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseReload(false)
+ return true
+ }
else -> {
// Check the time lock before launching settings
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
index 4418e86f7..5d4f1b007 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
+ * Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.settings
import android.app.Activity
import android.app.backup.BackupManager
-import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -37,6 +36,7 @@ import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionError
@@ -95,12 +95,28 @@ open class SettingsActivity
backupManager = BackupManager(this)
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
- // Call result in fragment
- (supportFragmentManager
- .findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
- ?.onProgressDialogThreadResult(actionTask, result)
+ when (actionTask) {
+ DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
+ // Reload the current activity
+ startActivity(intent)
+ finish()
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
+ }
+ else -> {
+ // Call result in fragment
+ (supportFragmentManager
+ .findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
+ ?.onProgressDialogThreadResult(actionTask, result)
+ coordinatorLayout?.showActionError(result)
+ }
+ }
+ }
- coordinatorLayout?.showActionError(result)
+ // To reload the current screen
+ if (intent.extras?.containsKey(FRAGMENT_ARG) == true) {
+ intent.extras?.getString(FRAGMENT_ARG)?.let { fragmentScreenName ->
+ onNestedPreferenceSelected(NestedSettingsFragment.Screen.valueOf(fragmentScreenName), true)
+ }
}
}
@@ -193,25 +209,33 @@ open class SettingsActivity
hideOrShowLockButton(NestedSettingsFragment.Screen.APPLICATION)
}
- private fun replaceFragment(key: NestedSettingsFragment.Screen) {
- supportFragmentManager.beginTransaction()
- .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
+ private fun replaceFragment(key: NestedSettingsFragment.Screen, reload: Boolean) {
+ supportFragmentManager.beginTransaction().apply {
+ if (reload) {
+ setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out,
R.anim.slide_in_left, R.anim.slide_out_right)
- .replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
- .addToBackStack(TAG_NESTED)
- .commit()
+ } else {
+ setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
+ R.anim.slide_in_left, R.anim.slide_out_right)
+ }
+ replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
+ addToBackStack(TAG_NESTED)
+ commit()
+ }
toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key)
+ // To reload the current screen
+ intent.putExtra(FRAGMENT_ARG, key.name)
hideOrShowLockButton(key)
}
- override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen) {
+ override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
if (mTimeoutEnable)
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
- replaceFragment(key)
+ replaceFragment(key, reload)
}
else
- replaceFragment(key)
+ replaceFragment(key, reload)
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -226,6 +250,7 @@ open class SettingsActivity
private const val SHOW_LOCK = "SHOW_LOCK"
private const val TITLE_KEY = "TITLE_KEY"
private const val TAG_NESTED = "TAG_NESTED"
+ private const val FRAGMENT_ARG = "FRAGMENT_ARG"
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
val intent = Intent(activity, SettingsActivity::class.java)
diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt b/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt
index 59c97ac7c..3dcfcf7b4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt
@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
cancelAll()
}
// Clear data
- Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
+ Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt
index 974eab131..e0d3767dc 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt
@@ -48,8 +48,8 @@ import java.util.*
class EntryContentsView @JvmOverloads constructor(context: Context,
- var attrs: AttributeSet? = null,
- var defStyle: Int = 0)
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle) {
private var fontInVisibility: Boolean = false
@@ -306,7 +306,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
allowCopy: Boolean,
onCopyButtonClickListener: OnClickListener?) {
- val entryCustomField: EntryField? = EntryField(context, attrs, defStyle)
+ val entryCustomField: EntryField? = EntryField(context)
entryCustomField?.apply {
setLabel(title)
setValue(value.toString(), value.isProtected)
diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt
index 65b8c9e5b..6d9df989c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt
@@ -23,7 +23,6 @@ import android.content.Context
import android.net.Uri
import android.text.format.Formatter
import androidx.documentfile.provider.DocumentFile
-import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil
import java.io.Serializable
import java.text.DateFormat
@@ -58,7 +57,11 @@ class FileDatabaseInfo : Serializable {
}
private set
- fun getModificationString(): String? {
+ fun getLastModification(): Long? {
+ return documentFile?.lastModified()
+ }
+
+ fun getLastModificationString(): String? {
return documentFile?.lastModified()?.let {
if (it != 0L) {
DateFormat.getDateTimeInstance()
@@ -69,6 +72,10 @@ class FileDatabaseInfo : Serializable {
}
}
+ fun getSize(): Long? {
+ return documentFile?.length()
+ }
+
fun getSizeString(): String? {
return documentFile?.let {
Formatter.formatFileSize(context, it.length())
diff --git a/app/src/main/res/drawable/ic_reload_white_24dp.xml b/app/src/main/res/drawable/ic_reload_white_24dp.xml
new file mode 100644
index 000000000..020472b89
--- /dev/null
+++ b/app/src/main/res/drawable/ic_reload_white_24dp.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/src/main/res/menu/contribution.xml b/app/src/main/res/menu/contribution.xml
index b76875ff9..5c1671456 100644
--- a/app/src/main/res/menu/contribution.xml
+++ b/app/src/main/res/menu/contribution.xml
@@ -22,6 +22,6 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/database.xml b/app/src/main/res/menu/database.xml
index d94f86817..a7a182b8a 100644
--- a/app/src/main/res/menu/database.xml
+++ b/app/src/main/res/menu/database.xml
@@ -24,4 +24,9 @@
android:title="@string/menu_save_database"
android:orderInCategory="95"
app:showAsAction="ifRoom" />
+
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 20464124d..2ad17ae3e 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -212,11 +212,11 @@
Zamknout
Zámek obrazovky
Při zhasnutí obrazovky uzamknout databázi
- Pokročilé odemčení
+ Rozšířené odemknutí
Biometrické odemčení
Nechá otevřít databázi snímáním biometrického údaje
Smazat šifrovací klíče
- Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí
+ Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí
Tuto funkci se nedaří spustit.
V zařízení je instalován Android %1$s, ale potřebná je verze %2$s a novější.
Hardware nebyl rozpoznán.
@@ -353,10 +353,10 @@
Aktualizovat
Zavři kolonky
Nelze vytvořit databázi s tímto heslem a klíčem ze souboru.
- Pokročilé odemčení
+ Rozšířené odemknutí
Biometrika
Automaticky otevřít pobídku
- Automaticky žádat pokročilé odemknutí, je-li databáze nastavena k jejímu použití
+ Automaticky žádat rozšířené odemknutí, je-li databáze nastavena k jejímu použití
Zapnout
Vypnout
Hlavní klíč
@@ -385,7 +385,7 @@
Opravit chybu založením nového UUID pro duplikáty a pokračovat\?
Databáze otevřena
Kopírujte pole záznamů pomocí schránky Vašeho zařízení
- K snadnějšímu otevření databáze použijte pokročilé odemknutí
+ K snadnějšímu otevření databáze použijte rozšířené odemknutí
Komprese dat
Komprese dat snižuje velikost databáze
Maximální počet
@@ -517,20 +517,31 @@
Vybrat záznam
Zpět na předchozí klávesnici
Vlastní položky
- Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí\?
+ Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí\?
Dovolí pro otevření databáze použít heslo Vašeho zařízení
Odemknutí heslem zařízení
Heslo zařízení
- Zadejte heslo a klikněte na tlačítko \"Pokročilé odemknutí\".
- Nelze inicializovat pobídku pro pokročilé odemknutí.
- Chyba při pokročilém odemknutí: %1$s
- Otisk pro pokročilé odemknutí nebyl rozpoznán
- Nelze načíst klíč pokročilého odemknutí. Prosím, smažte jej a opakujte proces rozpoznání uzamknutí.
- Načíst důvěrný údaj pomocí dat pokročilého odemknutí
- Otevřít databázi pomocí rozpoznání pokročilého odemknutí
- Varování: Pokud použijete rozpoznání pokročilého odemknutí, musíte si i nadále pamatovat hlavní heslo.
- Rozpoznání pokročilého odemknutí
- Pro uložení důvěrných údajů otevřete pobídku pokročilého odemknutí
- Pro odemknutí databáze otevřete pobídku pokročilého odemknutí
- Smazat klíč pokročilého odemknutí
+ Zadejte heslo a klikněte na tlačítko \"Rozšířené odemknutí\".
+ Nelze inicializovat pobídku pro rozšířené odemknutí.
+ Chyba při rozšířeném odemknutí: %1$s
+ Otisk pro rozšířené odemknutí nebyl rozpoznán
+ Nelze načíst klíč rozšířeného odemknutí. Prosím, smažte jej a opakujte proces rozpoznání odemknutí.
+ Načíst důvěrný údaj pomocí dat rozšířeného odemknutí
+ Otevřít databázi pomocí rozpoznání rozšířeného odemknutí
+ Varování: Pokud použijete rozpoznání rozšířeného odemknutí, musíte si i nadále pamatovat hlavní heslo.
+ Rozpoznání rozšířeného odemknutí
+ Pro uložení důvěrných údajů otevřete pobídku rozšířeného odemknutí
+ Pro odemknutí databáze otevřete pobídku rozšířeného odemknutí
+ Smazat klíč rozšířeného odemknutí
+ Rozšířené odemknutí databáze
+ Timeout rozšířeného odemknutí
+ Trvání použití rozšířeného odemknutí než bude obsah téhož smazán
+ Za účelem rozšířeného odemknutí neukládat žádný šifrovaný obsah
+ Přechodné rozšířené odemknutí
+ Pro odstranění klíčů rozšířeného odemknutí klepnout
+ Argon2id
+ Argon2d
+ Abyste rychle odemknuli databázi, propojte své heslo s naskenovanou biometrikou nebo údaji zámku zařízení.
+ Vypršení pokročilého odemknutí
+ Obsah
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index b686744e9..c0f30e763 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -118,8 +118,8 @@
Nie
Keine Suchergebnisse
Bitte einen Webbrowser installieren, um diese URL zu öffnen.
- Papierkorb/Sicherung nicht durchsuchen
- Die Gruppen „Sicherung“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt
+ Recycle bin und Backup nicht durchsuchen
+ Die Gruppen „Backup“ und „Recycle bin“ werden bei der Suche nicht berücksichtigt
Schnellsuche
Beim Öffnen einer Datenbank eine Suche veranlassen
Neue Datenbank anlegen …
@@ -218,7 +218,7 @@
Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen
Zwischenablage
Verschlüsselungsschlüssel löschen
- Alle mit der biometrischen Erkennung verbundenen Verschlüsselungsschlüssel löschen.
+ Lösche alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen
Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.
Keine entsprechende Hardware.
Papierkorb-Nutzung
@@ -231,7 +231,7 @@
Datenbankbeschreibung
Datenbankversion
Text
- App
+ Interface
Andere
Tastatur
Magikeyboard
@@ -267,7 +267,7 @@
Mitmachen
Mithelfen, um Stabilität und Sicherheit zu verbessern und weitere Funktionen zu ermöglichen.
Anders als viele andere Passwortmanager ist dieser <strong>werbefrei</strong>, <strong>quelloffen</strong> und unter einer <strong>Copyleft-Lizenz</strong>. Es werden keine persönlichen Daten gesammelt, in welcher Form auch immer, unabhängig von der verwendeten Version (kostenlos oder Pro).
- Mit dem Kauf der Pro-Version erhält man Zugang zu diesem <strong>visuellen Stil</strong> und unterstützt insbesondere <strong>die Umsetzung gemeinschaftlicher Projektarbeiten.</strong>
+ Mit dem Kauf der Pro-Version erhalten Sie Zugriff auf diesen <strong>visuellen Stil</strong> und unterstützen insbesondere <strong>die Umsetzung gemeinschaftlicher Projekte.</strong>
Dieser <strong>visuelle Stil</strong> wurde wegen Ihrer Großzügigkeit freigeschaltet.
Um unsere Freiheit zu bewahren und immer aktiv zu bleiben, zählen wir auf Ihren <strong>Beitrag.</strong>
Diese Funktion ist <strong>in Entwicklung</strong> und erfordert <strong>Ihren Beitrag</strong>, um bald verfügbar zu sein.
@@ -276,7 +276,7 @@
Sie ermutigen die Entwickler:innen, <strong>neue Funktionen</strong> einzuführen und gemäß Ihren Anmerkungen <strong>Fehler zu beheben</strong>.
Vielen Dank für Ihre Unterstützung.
Wir bemühen uns, diese Funktion bald zu veröffentlichen.
- Vergessen Sie nicht, Ihre Anwendung aktuell zu halten, indem Sie neue Versionen installieren.
+ Denken Sie daran, Ihre App auf dem neuesten Stand zu halten, indem Sie neue Versionen installieren.
Download
Unterstützen
ChaCha20
@@ -299,10 +299,10 @@
Schreibgeschützt
Schreibschutz der Datenbank aktivieren
Datenbank standardmäßig schreibgeschützt öffnen
- Den Öffnungsmodus für die Sitzung ändern.
-\n
-\n„Schreibgeschützt“ verhindert unbeabsichtigte Änderungen an der Datenbank.
-\nMit „Änderbar“ können alle Elemente hinzugefügt, gelöscht oder geändert werden.
+ Den Öffnungsmodus für die Sitzung ändern.
+\n
+\n„Schreibgeschützt“ verhindert unbeabsichtigte Änderungen an der Datenbank.
+\nMit „Änderbar“ können Sie alle Elemente nach Belieben hinzufügen, löschen oder ändern.
Eintrag bearbeiten
Datenbank kann nicht geladen werden.
Laden des Schlüssels fehlgeschlagen. Bitte versuchen, die „Speicherplatznutzung“ von KDF zu verringern.
@@ -336,7 +336,7 @@
Speicherort zuletzt verwendeter Datenbanken anzeigen
Defekte Datenbankverknüpfungen ausblenden
Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden
- Nicht die Anwendung beenden …
+ App nicht beenden …
Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird
Beim Schließen löschen
Papierkorb
@@ -373,8 +373,8 @@
Biometrisch
Aktivieren
Deaktivieren
- Biometrische Abfrage automatisch öffnen
- Automatisch nach Biometrie fragen, wenn die Datenbank dafür eingerichtet ist
+ Abfrage automatisch öffnen
+ Automatisch nach der erweiterten Entsperrung fragen, wenn die Datenbank dafür eingerichtet ist
Hauptschlüssel
Sicherheit
Verlauf
@@ -500,7 +500,7 @@
%1$s hochladen
Anhang hinzufügen
Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind
- Die Datei trotzdem hinzufügen\?
+ Soll die Datei trotzdem hinzugefügt werden\?
Zeigt die mit einem Eintrag verknüpfte UUID an
UUID anzeigen
Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.
@@ -527,7 +527,7 @@
Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen
Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank
Löschen des Schlüssels zum erweiterten Entsperren
- Fortschrittliche Entsperrerkennung
+ Erweiterte Entsperrerkennung
Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.
Erweiterte Entsperrung der Datenbank
Verfallzeit der erweiterten Entsperrung
@@ -535,16 +535,16 @@
Verfall der erweiterten Entsperrung
Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen
Temporäre erweiterte Entsperrung
- Erlaubt Ihnn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden
+ Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden
Drücken um erweiterte Entsperrschlüssel zu löschen
Inhalt
- Öffne Datenbank mit fortgeschriitener Entsperrungs-Erkennung
+ Öffne Datenbank mit erweiterter Entsperrerkennung
Eingabetaste
Rücktaste
Wähle Eintrag
Zurück zur vorherigen Tastatur
Benutzerdefinierte Felder
- Löschen aller Schlüssel in Zusammenhang mit Erkennung des erweiterterten Entsperrens\?
+ Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?
Geräteanmeldedaten entsperren
Geräteanmeldedaten
Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.
@@ -553,4 +553,6 @@
Konnte den Abdruck des erweiterten Entsperrens nicht erkennen
Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.
Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren
-
\ No newline at end of file
+ Argon2id
+ Argon2d
+
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 2d7814339..b00a4a04e 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -541,4 +541,6 @@
Λήξη προηγμένου ξεκλειδώματος
Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος
Περιεχόμενα
+ Argon2id
+ Argon2d
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 1277a002e..d6a5f7d3d 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -19,9 +19,9 @@
Spanish translation by José I. Paños. Updated by David García-Abad (23-09-2013)
-->
- Commentario
+ Comentarios
Página de inicio
- Implementación para Android del gestor de contraseñas KeePass
+ Implementación en Android del gestor de contraseñas KeePass
Aceptar
Añadir entrada
Añadir grupo
@@ -31,30 +31,30 @@
Aplicación
Configuración de la aplicación
Paréntesis
- Explora ficheros con OpenIntents File Manager
+ Se requiere un administrador de archivos que acepte la acciones de intención ACTION_CREATE_DOCUMENT y ACTION_OPEN_DOCUMENT para crear, abrir y guardar los archivos de la base de datos.
Portapapeles limpiado
Portapapeles caducado
- Duración del almacenamiento en el portapapeles (si lo admite el dispositivo)
+ Duración del almacenamiento en el portapapeles (si lo admite su dispositivo)
Seleccionar para copiar %1$s en el portapapeles
- Creando clave de la base de datos…
+ Recuperando la clave de la base de datos…
Base de datos
Descifrando el contenido de la base de datos…
- Utilice como base de datos por defecto
+ Utilizar como base de datos por defecto
Dígitos
- KeePassDX © %1$d Kunzisoft es \u0020de <strong>código abierto</strong> y <strong>sin publicidad</strong>.
+ KeePassDX © %1$d Kunzisoft es de <strong>código abierto</strong> y <strong>sin publicidad</strong>.
\nSe proporciona tal cual, bajo licencia <strong>GPLv3</strong>, sin ninguna garantía.
Abrir base de datos existente
- Acceso
+ Accedido
Cancelar
Notas
Confirmar contraseña
- Creación
- Caducidad
+ Creado
+ Caduca
Archivo de clave
- Modificación
+ Modificada
Contraseña
Guardar
- Nombre
+ Título
URL
Nombre de usuario
No se admite el cifrador de flujo Arcfour.
@@ -64,11 +64,11 @@
Asegúrese de que la ruta sea correcta.
Proporcione un nombre.
Seleccione un archivo de clave.
- No queda memoria para cargar toda la base de datos.
+ No hay memoria para cargar toda la base de datos.
Debe seleccionar al menos un tipo de generación de contraseñas.
Las contraseñas no coinciden.
Pasadas demasiado grande. Establecido a 2147483648.
- Proporcione un número entero positivo en el campo «Longitud».
+ Introduzca un número entero positivo en el campo \"Longitud\".
Explorador de archivos
Generar contraseña
Confirmar contraseña
@@ -78,7 +78,7 @@
Longitud
Contraseña
Contraseña
- Contraseña o archivo de clave no válido.
+ No se pudieron leer las credenciales.
No se pudo reconocer el formato de la base de datos.
Longitud
Tamaño de los elementos de la lista
@@ -103,7 +103,7 @@
Menos
Nunca
Sin resultado de búsqueda
- Instale un navegador web para abrir este URL.
+ Instale un navegador web para abrir esta URL.
No buscar en las entradas de respaldo
Omite los grupos «Respaldo» y «Papelera de reciclaje» de los resultados de búsqueda
Creando nueva base de datos…
@@ -124,7 +124,9 @@
No se admite esta versión de la base de datos.
Mayúsculas
Versión %1$s
- Introduzca una contraseña y/o un archivo de clave para desbloquear su base de datos.
+ Introduzca la contraseña y/o el archivo clave para desbloquear su base de datos.
+\n
+\nHaga una copia de seguridad de su archivo de base de datos en un lugar seguro después de cada cambio.
- 5 segundos
- 10 segundos
@@ -146,28 +148,28 @@
ASCII extendido
Permitir
Error del portapapeles
- Algunos dispositivos Android tienen un error en la implementación del portapapeles que provoca fallos al copiar desde las aplicaciones.
- Falló la limpieza del portapapeles
+ Algunos dispositivos no permiten que las aplicaciones usen el portapapeles.
+ No se pudo limpiar el portapapeles
Cada cadena debe tener un nombre de campo.
El servicio de autocompletado no se ha podido habilitar.
Nombre del campo
Valor del campo
No se pudo encontrar el archivo. Intente volver a abrirlo en el explorador de archivos.
- El algoritmo es incorrecto.
+ Algoritmo incorrecto.
El archivo de clave está vacío.
Copia de %1$s
Llenado de formulario
Protección
Protegida contra escritura
- KeePassDX necesita permiso de escritura para modificar la base de datos.
- Algoritmo de cifrado utilizado en todos los datos.
+ Dependiendo del administrador de archivos, puede que KeePassDX no permita escribir en su almacenamiento.
+ Algoritmo de cifrado utilizado para todos los datos.
Para generar la clave del algoritmo de cifrado, la clave maestra se transforma mediante una función de derivación de claves con una sal aleatoria.
Uso de memoria
Cantidad de memoria (en bytes) que usará la función de derivación de clave.
Paralelismo
Grado de paralelismo (p. ej. número de hilos) usados por la función de derivación de clave.
Ordenar
- Inferiores primero ↓
+ Más bajo primero ↓
Agrupar antes
Papelera debajo
Título
@@ -177,8 +179,8 @@
Acceso
Resultados de búsqueda
Atención
- Datos de entrada no encontrados.
- Evite emplear en la base de datos caracteres que no pertenezcan al formato de codificación del texto (los caracteres no reconocidos se convierten a la misma letra).
+ No se pudieron encontrar los datos de entrada.
+ Evite los caracteres de la contraseña fuera del formato de codificación de texto en el archivo de la base de datos (los caracteres no reconocidos se convierten a la misma letra).
¿Continuar sin la protección de desbloqueo de contraseña\?
¿Continuar sin clave de cifrado\?
Contraseña cifrada almacenada
@@ -197,25 +199,25 @@
Establecer los caracteres permitidos del generador de contraseñas
Portapapeles
Notificaciones del portapapeles
- Mostrar notificaciones del portapapeles para copiar campos al examinar una entrada
+ Mostrar las notificaciones del portapapeles para copiar campos al examinar una entrada
Bloquear
Bloqueo de pantalla
Bloquear la base de datos cuando la pantalla esté apagada
Desbloqueo avanzado
Desbloqueo biométrico
- Le permite escanear su huella dactilar u otro dato biométrico para abrir la base de datos
+ Le permite escanear sus datos biométricos para abrir la base de datos
Eliminar claves de cifrado
- Eliminar todas las claves de cifrado relacionadas con el reconocimiento biométrico
+ Eliminar todas las claves de cifrado relacionadas con el reconocimiento de desbloqueo avanzado
No se pudo iniciar esta funcionalidad.
- La versión de Android del dispositivo es %1$s, pero es necesaria la %2$s o posterior.
- No se encontró el hardware correspondiente.
+ El dispositivo funciona con Android %1$s, pero necesita %2$s o posterior.
+ No se pudo encontrar el hardware correspondiente.
Nombre del archivo
Ruta
Asignar una clave maestra
- Crear base de datos nueva
+ Crear una base de datos nueva
Uso de la papelera de reciclaje
Mueve los grupos y las entradas al grupo \"Papelera de reciclaje\" antes de eliminarlos
- Fuente de los campos
+ Tipografía del campo
Cambiar la fuente de los campos para una mejor visibilidad del carácter
Portapapeles de confianza
Permitir copiar la contraseña de entrada y los campos protegidos al portapapeles
@@ -223,11 +225,11 @@
Descripción de la base de datos
Versión de la base de datos
Texto
- Aplicación
+ Interfaz
Otro
Teclado
Teclado mágico
- Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente.
+ Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente
Restablecer sugerencias didácticas
Mostrar de nuevo toda la información didáctica
Se restablecieron las sugerencias didácticas
@@ -259,20 +261,20 @@
Participar
Participe para aumentar la estabilidad, la seguridad y agregar más funciones.
A diferencia de muchas aplicaciones de administración de contraseñas, esta es <strong>sin publicidad</strong>, <strong>software libre con copyleft</strong> y no recopila datos personales en sus servidores, sin importar la versión que use.
- Al comprar la versión pro, tendrá acceso a <strong>la característica visual </strong>y usted ayudará especialmente a <strong>la realización de proyectos comunitarios.</strong>
- Esta <strong>característica visual </strong>está disponible gracias a tu generosidad.
+ Al comprar la versión pro, tendrá acceso al <strong>estilo visual </strong>y ayudará especialmente a <strong>la realización de proyectos comunitarios.</strong>
+ Este <strong>estilo visual </strong>está disponible gracias a tu generosidad.
Para mantener nuestra libertad y estar siempre vigente, contamos con tu <strong>contribución.</strong>
Esta función está <strong>en desarrollo</strong> y requiere de tu <strong>contribución</strong> para estar disponible dentro de poco.
Al comprar la versión <strong>pro</strong>,
Al <strong>contribuir</strong>,
- usted alienta a los desarrolladores a crear <strong>nuevas funciones</strong> y a <strong>errores de configuración</strong> de acuerdo con tus comentarios.
+ anima a los desarrolladores a crear <strong>nuevas funciones</strong> y a <strong>corregir errores</strong> de acuerdo con sus comentarios.
Muchas gracias por tu contribución.
Estamos trabajando duro para lanzar esta característica rápidamente.
- No olvide mantener su aplicación actualizada.
+ Recuerde mantener su aplicación actualizada instalando nuevas versiones.
Descargar
Contribuir
ChaCha20
- AES-KDF
+ AES
Tema de aplicación
Tema utilizado en la aplicación
Seleccione un paquete de iconos
@@ -280,7 +282,7 @@
Editar entrada
No se pudo cargar la base de datos.
No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.
- No se puede mover un grupo dentro de sí mismo.
+ No puede mover un grupo dentro de sí mismo.
Enseña nombres de usuario
Enseña nombres de usuador en las listras de entradas
Copiar
@@ -291,13 +293,13 @@
Modificable
Compilación %1$s
Si la eliminación del cortapapeles falla, elimine su historial manualmente.
- ATENCIÓN: todas las aplicaciones comparten el portapapeles. Si copia datos confidenciales, otros programas podrían recuperarlos.
+ Advertencia: El portapapeles es compartido por todas las aplicaciones. Si se copian datos sensibles, otros programas pueden recuperarlos.
No permitir claves maestras
- Activar el botón «Abrir» si no se selecciona ninguna credencial
- Pantallas didácticas
- Resaltar los elementos para conocer cómo funciona la aplicación
+ Permite pulsar el botón \"Abrir\" si no se seleccionan credenciales
+ Sugerencias educativas
+ Destacar elementos para aprender cómo funciona la aplicación
Protegida contra escritura
- Abrir su base de datos protegida contra escritura de manera predeterminada
+ Abrir la base de datos de solo lectura por defecto
Proteja la base de datos contra escritura
Teclado mágico
Teclado mágico (KeePassDX)
@@ -315,56 +317,56 @@
Apariencia
Tema del teclado
Teclas
- Vibrar al pulsar
- Emitir sonido al pulsar
+ Vibrar al pulsar tecla
+ Sonar al pulsar tecla
Modo de selección
No cierre la aplicación…
- Bloquear la base de datos cuando se pulsa el botón Atrás en la pantalla inicial
+ Bloquear la base de datos cuando el usuario pulse el botón atrás en la pantalla inicial
Vaciar al cerrar
- Bloquear la base de datos cuando la duración del portapapeles expire o la notificación sea cerrada
+ Bloquear la base de datos cuando expire la duración del portapapeles o cuando se cierre la notificación después de empezar a utilizarla
Papelera de reciclaje
Selección de entrada
Mostrar campos de entrada en el Teclado mágico al ver una entrada
Eliminar contraseña
- Elimina la contraseña proporcionada tras un intento de conexión
+ Elimina la contraseña introducida tras un intento de conexión a una bse de datos
Abrir archivo
- Elementos secundarios del nodo
+ Nodos hijo
Añadir nodo
Añadir entrada
Añadir grupo
- Información de archivo
- Casilla de contraseña
- Casilla de archivo de clave
+ Información del archivo
+ Casilla de verificación de la contraseña
+ Casilla de verificación del archivo de clave
Icono de entrada
Generador de contraseñas
Longitud de contraseña
Añadir campo
- Eliminar campo
+ Eliminar el campo
UUID
- No se puede mover una entrada aquí.
- No se puede copiar una entrada aquí.
- Mostrar cantidad de entradas
- Mostrar la cantidad de entradas en un grupo
+ No puede mover una entrada aquí.
+ No puede copiar una entrada aquí.
+ Mostrar el número de entradas
+ Mostrar el número de entradas en un grupo
Fondo
Actualizar
Cerrar campos
No se puede crear la base de datos con esta contraseña y este archivo de clave.
Desbloqueo avanzado
Biometría
- Abrir petición de datos biométricos automáticamente
- Abrir automáticamente la petición de datos biométricos cuando se define una clave biométrica para una base de datos
+ Abrir petición automáticamente
+ Solicitar automáticamente el desbloqueo avanzado si la base de datos está configurada para utilizarlo
Activar
Desactivar
Cambiar el modo de apertura de la sesión.
\n
\n\"Protegido contra escritura\" evita cambios no deseados en la base de datos.
-\n\"Modificable\" le permite agregar, eliminar o modificar todos los elementos.
- Presione hacia atrás en la raíz para bloquear
+\n\"Modificable\" le permite agregar, eliminar o modificar todos los elementos como desee.
+ Presione \'Atrás\' para bloquear
Repetir la visibilidad de la contraseña
- Llave maestra
+ Clave maestra
Seguridad
Historial
- Configuración de contraseña de un solo uso
+ Establecer una contraseña de un solo uso
Tipo de contraseña de un solo uso
Secreto
Período (segundos)
@@ -377,16 +379,16 @@
Clave secreta debe estar en formato Base32.
Contador debe estar entre %1$d y %2$d.
No se puede guardar la base de datos.
- Este texto no encaja con la información requerida.
+ Este texto no coincide con el elemento requerido.
No fue posible crear el archivo de base de datos.
- Parar lograr <strong>mantener nuestra libertad</strong>, <strong>corregir errores</strong>, <strong>agregar características</strong> y <strong>siempre estar activos</strong>, contamos con tu <strong>contribución</strong>.
+ Parar lograr <strong>mantener nuestra libertad</strong>, <strong>corregir errores</strong>, <strong>agregar características</strong> y <strong>estar siempre activos</strong>, contamos con tu <strong>contribución</strong>.
Añadir elemento
- Descarga completa!
+ ¡Completado!
Finalizando…
En progreso: %1$d%%
Inicializando…
Descargar %1$s
- Guardar la base de datos tras acciones importantes (en modo \"Modificable\")
+ Guardar la base de datos después de cada acción importante (en modo \"Modificable\")
Guardar base de datos automáticamente
Bloquear autocompletado
Cambiar teclado
@@ -395,12 +397,12 @@
Ninguna
Compresión
Nombre de usuario predeterminado
- Requerir cambiar la contraseña maestra la próxima vez (una sola vez)
+ Requerir cambiar la contraseña maestra la próxima vez (una vez)
Forzar renovación la próxima vez
- Requerir un cambio de contraseña maestra (días)
+ Requerir un cambio de la contraseña maestra (días)
Forzar renovación
Tamaño máximo
- Usar desbloqueo avanzado para abrir una base de datos de manera más fácil
+ Usar el desbloqueo avanzado para abrir una base de datos más fácilmente
Muestra el botón de bloqueo en la interfaz
Mostrar botón de bloqueo
Configuración de autocompletado
@@ -409,8 +411,8 @@
Otorga acceso de escritura para guardar cambios en la base de datos
Mostrar ubicaciones de bases de datos recientes
Mostrar archivos recientes
- Recordar la ubicación de las bases de datos
- Guardar ubicaciones de bases de datos
+ Lleva un registro de los lugares donde se almacenan las bases de datos
+ Recordar las ubicaciones de las bases de datos
La base de datos contiene UUIDs duplicados.
Restaurar historial
Vaciar papelera de reciclaje
@@ -419,12 +421,12 @@
%1$s con la misma UUID %2$s ya existe.
Esta etiqueta ya existe.
Descartar
- ¿Descartar cambios\?
+ ¿Descartar los cambios\?
Validar
Contribución
- Contactar
- Período debe estar entre %1$d y %2$d segundos.
- No se puede copiar un grupo aquí.
+ Contacto
+ El período debe estar entre %1$d y %2$d segundos.
+ No puede copiar un grupo aquí.
La compresión de datos reduce el tamaño de la base de datos
Compresión de datos
No se recomienda agregar un archivo de claves vacío.
@@ -433,19 +435,19 @@
Al cargar este archivo reemplazará el existente.
¿Borrar los nodos seleccionados de forma permanente\?
El acceso al archivo fue revocado por el administrador de archivos
- Ejecutando el comando …
+ Ejecutando el comando…
Ocultar enlaces rotos en la lista de bases de datos recientes
Ocultar enlaces de bases de datos rotos
Realiza un seguimiento de dónde se almacenan los archivos de claves
- Recuerdar las ubicaciones de los archivos de claves
+ Recordar las ubicaciones de los archivos de clave
Buscar dominios web con restricciones de subdominios
Búsqueda de subdominio
Solicite una búsqueda al abrir una base de datos
Búsqueda rápida
- Borrar historial
- El token debe tener de %1$d a %2$d dígitos.
+ Eliminar el historial
+ El token debe contener de %1$d a %2$d dígitos.
Archivos adjuntos
- Adjuntar
+ Añadir el archivo adjunto
Información de credenciales
Base de datos abierta
Adjuntar
@@ -455,4 +457,92 @@
Se supone que una base de datos KeePass contiene solo pequeños archivos de utilidad (como archivos de clave PGP).
\n
\nLa base de datos puede volverse muy grande y reducir su rendimiento con esta subida.
+ Grupo de la papelera de reciclaje
+ Filtrar
+ Elimina los archivos adjuntos contenidos en la base de datos pero no vinculados a una entrada
+ Eliminar los datos no vinculados
+ Datos
+ ¿Borrar todas las claves de encriptación relacionadas con el reconocimiento de desbloqueo avanzado\?
+ Tiempo límite de desbloqueo avanzado
+ Duración del uso de desbloqueo avanzado antes de borrar su contenido
+ Expiración de desbloqueo avanzado
+ No almacenar ningún contenido encriptado para utilizar el desbloqueo avanzado
+ Desbloqueo avanzado temporal
+ Le permite usar la credenciales de su dispositivo para abrir la base de datos
+ Desbloqueo de las credenciales del dispositivo
+ Toque para eliminar las teclas de desbloqueo avanzadas
+ Contenido
+ Copiar los campos de entrada usando el portapapeles de su dispositivo
+ Credenciales del dispositivo
+ Introduzca la contraseña y luego haga clic en el botón \"Desbloqueo avanzado\".
+ No se pudo inicializar el indicador de desbloqueo avanzado.
+ Error de desbloqueo avanzado: %1$s
+ No se pudo reconocer la impresión de desbloqueo avanzado
+ No se pudo leer la llave de desbloqueo avanzada. Por favor, bórrela y repita el procedimiento de reconocimiento de desbloqueo.
+ Extraer la credencial de la base de datos con datos de desbloqueo avanzado
+ Abrir la base de datos con reconocimiento de desbloqueo avanzado
+ Advertencia: Aún debes recordar tu contraseña maestra si usas el reconocimiento de desbloqueo avanzado.
+ Reconocimiento de desbloqueo avanzado
+ Abrir el indicador de desbloqueo avanzado para almacenar las credenciales
+ Abrir el aviso de desbloqueo avanzado para desbloquear la base de datos
+ El almacén de claves no está debidamente inicializado.
+ Se requiere una actualización de la seguridad biométrica.
+ No se ha inscrito ninguna credencial biométrica o del dispositivo.
+ El contenido del archivo clave nunca debe modificarse y, en el mejor de los casos, debe contener datos generados al azar.
+ ¿Borrar permanentemente todos los nodos de la papelera de reciclaje\?
+ Modo de registro
+ Modo de guardado
+ Modo de búsqueda
+ ¿Resolver el problema generando nuevos UUID para que los duplicados continúen\?
+ Borrar la clave de desbloqueo avanzado
+ El nombre del campo ya existe.
+ Guardar un nuevo elemento no está permitido en una base de datos de sólo lectura
+ Recomendar la renovación
+ Limitar el tamaño del historial (en bytes) por entrada
+ Limitar el número de elementos del historial por entrada
+ Número máximo
+ Configuración del teclado del dispositivo
+ Color personalizado de la base de datos
+ Recomendar cambiar la contraseña maestra (días)
+ Notificación
+ Ocultar las entradas expiradas
+ Buscar información compartida
+ Argon2id
+ Argon2d
+ Subir %1$s
+ Configurar la gestión de contraseñas de una sola vez (HOTP / TOTP) para generar un token solicitado para la autenticación de dos factores (2FA).
+ Establecer la contaseña de un solo uso
+ Vincule su contraseña con su credencial biométrica o del dispositivo escaneada para desbloquear rápidamente su base de datos.
+ Desbloqueo avanzado de la base de datos
+ No se permite guardar datos en una base de datos abierta como de sólo lectura.
+ Reiniciar la aplicación que contiene el formulario para activar el bloqueo.
+ Lista de bloqueo que impide el llenado automático de los dominios web
+ Lista de bloqueo de los dominios web
+ Lista de bloqueo que impide el llenado automático de las aplicaciones
+ Lista de bloqueo de las aplicaciones
+ Pedir que se guarden los datos cuando se valide un formulario
+ Pedir que se guarden los datos
+ Intente guardar la información de la búsqueda cuando haga una selección de entrada manual
+ Guardar la información de la búsqueda
+ Sugerir automáticamente los resultados de la búsqueda desde el dominio web o el ID de la aplicación
+ Búsqueda automática
+ Cerrar la base de datos después de una selección de autocompletado
+ Cerrar la base de datos
+ Entrar
+ Retroceder
+ Seleccionar la entrada
+ Volver al teclado anterior
+ Campos personalizados
+ Cambiar automáticamente al teclado anterior después de bloquear la base de datos
+ Bloquear la base de datos
+ Cambiar automáticamente al teclado anterior después de ejecutar \"Acción de la tecla automática\"
+ Acción de la tecla automática
+ Cambiar automáticamente al teclado anterior en la pantalla de credenciales de la base de datos
+ Pantalla de credenciales de la base de datos
+ Acción de la tecla automática
+ Intentar guardar la información compartida cuando hagas una selección de entrada manual
+ Guardar información compartida
+ Buscar automáticamente la información compartida para llenar el teclado
+ Muestra el UUID vinculado a una entrada
+ Mostrar UUID
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 230fb746d..cfa735f30 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -550,4 +550,6 @@
Déverrouillage avancé temporaire
Appuyez pour supprimer les clés de déverrouillage avancées
Contenu
+ Argon2id
+ Argon2d
\ No newline at end of file
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index e92b3bfeb..490afc601 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -525,4 +525,6 @@
Nemoj spremati šifrirani sadržaj za napredno otključavanje
Sadržaj
Privremeno napredno otključavanje
+ Argon2id
+ Argon2d
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 2b6f112de..26af1de4d 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -401,9 +401,9 @@
Nascondi i collegamenti dei database corrotti
Mostra le posizioni dei database recenti
Mostra file recenti
- Ricorda la posizione dei file chiave
+ Ricorda posizione file chiave
Ricorda la posizione dei database
- Ricorda la posizione dei database
+ Ricorda posizione database
Per continuare, risolvi il problema generando nuovi UUID per i duplicati\?
Impossibile creare il file del database.
Aggiungi allegato
@@ -540,8 +540,10 @@
Credenziali del dispositivo
Inserisci la password, quindi clicca sull\'icona \"Sblocco avanzato\".
Errore sblocco avanzato: %1$s
- Apri il database autenticando con lo sblocco avanzato
+ Apri il database con lo sblocco avanzato
Autentica con lo sblocco avanzato per sbloccare il database
Autentica con lo sblocco avanzato per salvare le credenziali
Elimina chiave di sblocco avanzato
+ Argon2id
+ Argon2d
\ No newline at end of file
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 312b0707b..5ab9f1737 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -388,4 +388,12 @@
രജിസ്ട്രേഷൻ മോഡ്
തിരയൽ മോഡ്
read-only ഡാറ്റാബേസിൽ പുതിയ ഒരു ഇനം സംരക്ഷിക്കാൻ കഴിയില്ല
+ വിപുലമായ ഡാറ്റാബേസ് അൺലോക്ക്
+ എൻട്രി തിരഞ്ഞെടുക്കുക
+ ഇഷ്ടാനുസൃത ഫീൽഡുകൾ
+ കീകൾ
+ അറിയിപ്പ്
+ പുതുക്കൽ ശുപാർശ ചെയ്യുക
+ ഉള്ളടക്കം
+ ഉപകരണ ക്രെഡൻഷ്യൽ
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index c9b233a3b..7e2a9f571 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -535,4 +535,5 @@
Nie przechowuj żadnych zaszyfrowanych treści, aby korzystać z zaawansowanego odblokowywania
Naciśnij, aby usunąć zaawansowane klucze odblokowujące
Zawartość
+ Otwórz bazę danych z zaawansowanym rozpoznawaniem odblokowania
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 46079eb05..a7e21d4a4 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -519,10 +519,10 @@
Невозможно распознать расширенную разблокировку
Невозможно прочитать ключ расширенной разблокировки. Удалите его и повторите процедуру распознавания разблокировки.
Извлекать учётные данные базы с использованием расширенной разблокировки
- Открывать базу с расширенным распознаванием разблокировки
+ Открыть базу с расширенным распознаванием разблокировки
Предупреждение: даже при использовании расширенной разблокировки вам всё равно необходимо помнить главный пароль.
- Открывать запрос расширенной разблокировки для сохранения учётных данных
- Открывать запрос расширенной разблокировки для разблокировки базы
+ Открыть запрос расширенной разблокировки для сохранения учётных данных
+ Открыть запрос расширенной разблокировки для разблокировки базы
Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки\?
Ошибка расширенной разблокировки: %1$s
Распознавание расширенной разблокировки
@@ -541,4 +541,6 @@
Не сохранять зашифрованное содержимое для использования расширенной разблокировки
Нажмите, чтобы удалить ключи расширенной разблокировки
Содержимое
+ Argon2ID
+ Argon2D
\ No newline at end of file
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index aae3a6f80..ef3fe1f01 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -525,4 +525,6 @@
Geçici gelişmiş kilit açma
Gelişmiş kilit açma anahtarlarını silmek için dokunun
İçerik
+ Argon2id
+ Argon2d
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 51ba1f330..064cdb1a2 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -541,4 +541,6 @@
Тимчасове розширене розблокування
Торкнутися, щоб видалити клавіші розширеного розблокування
Вміст
+ Argon2id
+ Argon2d
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 7f0f0221f..76832e2d0 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -541,4 +541,6 @@
临时性高级解锁
点击删除高级解锁密钥
内容
+ Argon2id
+ Argon2d
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 80f574349..b1b244d63 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -134,6 +134,8 @@
This text does not match the requested item.
Saving a new item is not allowed in a read-only database
The field name already exists.
+ Database URI cannot be retrieved.
+ Unable to properly rebuild the list.
Field name
Field value
Could not find file. Try reopening it from your file browser.
@@ -183,6 +185,7 @@
Hide password
Lock database
Save database
+ Reload database
Open
Search
Show password
@@ -270,6 +273,9 @@
Remove this data anyway?
It is not recommended to add an empty keyfile.
The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.
+ The information contained in your database file has been modified outside the app.
+ Overwrite the external modifications by saving the database or reload it with the latest changes.
+ Access to the file revoked by the file manager, close the database and reopen it from its location.
Version %1$s
Build %1$s
No biometric or device credential is enrolled.
diff --git a/build.gradle b/build.gradle
index cde378402..8e5371d69 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.4.10'
+ ext.kotlin_version = '1.4.21'
repositories {
jcenter()
google()
diff --git a/fastlane/metadata/android/en-US/changelogs/50.txt b/fastlane/metadata/android/en-US/changelogs/50.txt
index 42780ecb1..457eb4856 100644
--- a/fastlane/metadata/android/en-US/changelogs/50.txt
+++ b/fastlane/metadata/android/en-US/changelogs/50.txt
@@ -1 +1 @@
- *
\ No newline at end of file
+ * Fix KeyFile bug #820
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/51.txt b/fastlane/metadata/android/en-US/changelogs/51.txt
new file mode 100644
index 000000000..25c598fe9
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/51.txt
@@ -0,0 +1,2 @@
+ * Fix small bugs
+ * Remove write permission since Android 10
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/52.txt b/fastlane/metadata/android/en-US/changelogs/52.txt
new file mode 100644
index 000000000..5edd71c36
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/52.txt
@@ -0,0 +1,2 @@
+ * Fix specific attachments with kdbx3.1 databases #828
+ * Fix small bugs
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/53.txt b/fastlane/metadata/android/en-US/changelogs/53.txt
new file mode 100644
index 000000000..73014adc0
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/53.txt
@@ -0,0 +1 @@
+ * Detect file changes and reload database #794
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/50.txt b/fastlane/metadata/android/fr-FR/changelogs/50.txt
index 42780ecb1..45cc3d57d 100644
--- a/fastlane/metadata/android/fr-FR/changelogs/50.txt
+++ b/fastlane/metadata/android/fr-FR/changelogs/50.txt
@@ -1 +1 @@
- *
\ No newline at end of file
+ * Correction du bug de fichier de clé #820
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/51.txt b/fastlane/metadata/android/fr-FR/changelogs/51.txt
new file mode 100644
index 000000000..18233cde2
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/51.txt
@@ -0,0 +1,2 @@
+ * Correction de petits bugs
+ * Suppression des permissions d'écriture à partir d'Android 10
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/52.txt b/fastlane/metadata/android/fr-FR/changelogs/52.txt
new file mode 100644
index 000000000..c3ebc1548
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/52.txt
@@ -0,0 +1,2 @@
+ * Correction des pièces jointes spécifiques avec les bases kdbx3.1 #828
+ * Correction de petits bugs
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/53.txt b/fastlane/metadata/android/fr-FR/changelogs/53.txt
new file mode 100644
index 000000000..2affba64f
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/53.txt
@@ -0,0 +1 @@
+ * Détection des changements de fichiers et rechargement de base de données #794
\ No newline at end of file
diff --git a/fastlane/pro/metadata/android/en-US/full_description.txt b/fastlane/pro/metadata/android/en-US/full_description.txt
index 144c9e3c7..9ecd13423 100644
--- a/fastlane/pro/metadata/android/en-US/full_description.txt
+++ b/fastlane/pro/metadata/android/en-US/full_description.txt
@@ -1,7 +1,7 @@
Multi-format KeePass password manager, the app allows saving and using passwords, keys and digital identities in a secure way, by integrating the Android design standards.
This pro version is under development, buying it encourages faster development, better service, and you contribute to the creation of open source softwares without advertising.
-Currently, the application has the same features as the free version with the themes unlocked but is intended to integrate the elements of connection and synchronization facilitated for sites and services commonly used.
+Currently, the application has the same features as the free version with the themes unlocked but is intended to integrate elements related to non-free sites and services commonly used.
Features
- Create database files / entries and groups.
diff --git a/fastlane/pro/metadata/android/fr-FR/full_description.txt b/fastlane/pro/metadata/android/fr-FR/full_description.txt
index 2969463ba..5342ba8db 100644
--- a/fastlane/pro/metadata/android/fr-FR/full_description.txt
+++ b/fastlane/pro/metadata/android/fr-FR/full_description.txt
@@ -1,7 +1,7 @@
Gestionnaire de mots de passe KeePass multiformats, l'application permet d'enregistrer et d'utiliser des mots de passe, des clés et des identités numériques de manière sécurisée, en intégrant les normes de conception Android.
Cette version pro est en cours de développement, en l'achetant vous encouragez un développement plus rapide, un meilleur service et vous contribuez à la création de logiciels open source sans publicité.
-Actuellement, l'application possède les mêmes fonctionnalités que la version gratuite avec les thèmes débloqués mais est destinée à intégrer les éléments de connexion et de synchronisation facilités pour les sites et services couramment utilisés.
+Actuellement, l'application possède les mêmes fonctionnalités que la version gratuite avec les thèmes débloqués mais est destinée à intégrer des éléments liés à des sites et services non gratuits couramment utilisés.
Fonctionnalités
- Création de bases de données / entrées et groupes.
diff --git a/fastlane/pro/metadata/android/ja-JP/full_description.txt b/fastlane/pro/metadata/android/ja-JP/full_description.txt
index 2ff47dfee..d6f7237e4 100644
--- a/fastlane/pro/metadata/android/ja-JP/full_description.txt
+++ b/fastlane/pro/metadata/android/ja-JP/full_description.txt
@@ -1,7 +1,7 @@
複数の形式に対応する KeePass パスワード マネージャー。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。
この pro バージョンは開発中です。購入することで開発の加速とサービスの改善を支援し、広告なしのオープンソース ソフトウェアの作成に貢献できます。
-現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。よく利用されるサイトやサービスの接続と同期を楽にする要素を統合することが予定されています。
+現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。一般的に使われている不自由なサイトやサービスに関連する要素を統合することを計画しています。
機能
- データベースファイル / エントリー・グループの作成