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 バージョンと同じです。一般的に使われている不自由なサイトやサービスに関連する要素を統合することを計画しています。 機能 - データベースファイル / エントリー・グループの作成