Compare commits

..

35 Commits
2.9.4 ... 2.9.5

Author SHA1 Message Date
J-Jamet
774dddca54 Merge branch 'release/2.9.5' 2020-12-16 17:05:47 +01:00
J-Jamet
de980d030a Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-12-16 11:13:48 +01:00
J-Jamet
0e859646fe Fix timeout reset #817 2020-12-15 19:40:27 +01:00
christopher robert
059c7b7713 Translated using Weblate (German)
Currently translated at 98.3% (490 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-15 13:29:10 +01:00
J-Jamet
5fb7bf71c8 Prevent auto switch back to previous keyboard if otp field exists #814 2020-12-15 11:47:57 +01:00
J-Jamet
8b0133ff7f Update CHANGELOG 2020-12-14 19:25:34 +01:00
J-Jamet
8d834946b8 Fix view flickering 2020-12-14 19:12:45 +01:00
J-Jamet
2f646395d4 Merge branch 'feature/Device_Unlock' into develop 2020-12-14 18:31:28 +01:00
J-Jamet
f6e79ba37b Add advanced unlock education hint 2020-12-14 18:23:41 +01:00
J-Jamet
e633c7a861 Biometric unlock in priority and device unlock when biometric not available 2020-12-14 17:51:18 +01:00
J-Jamet
dc02a8d78c Rollback to fix bug after orientation change 2020-12-14 16:59:38 +01:00
J-Jamet
baa9b88512 Check if current database is the same after activity result 2020-12-14 16:56:15 +01:00
J-Jamet
c522e87da8 Fix multiple methods in settings 2020-12-14 16:41:14 +01:00
Paul
ef5ebf2c15 Translated using Weblate (German)
Currently translated at 98.3% (490 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-14 13:23:34 +01:00
christopher robert
4b147e770c Translated using Weblate (German)
Currently translated at 98.3% (490 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-14 13:23:34 +01:00
J-Jamet
157a5c0b05 Refactor view visibility method 2020-12-13 23:39:17 +01:00
J-Jamet
f2288b0c64 Fix biometric prompt during orientation change 2020-12-13 23:25:44 +01:00
J-Jamet
d8506450aa Fix biometric orientation change 2020-12-13 23:19:04 +01:00
J-Jamet
f9b085e73f Fix min setting version and condition in Android R 2020-12-13 22:44:30 +01:00
J-Jamet
388cf6a91b Fix device credential condition and keep connexion if ActivityForResult requested 2020-12-13 22:33:58 +01:00
Stephan Paternotte
9e6e77b363 Translated using Weblate (Dutch)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-12-13 18:48:46 +01:00
J-Jamet
ec33ca8173 Refactoring Advanced unlock fragment and manager 2020-12-13 17:09:41 +01:00
J-Jamet
6be0457947 Add fragment 2020-12-13 14:18:21 +01:00
WaldiS
f3b84aa845 Translated using Weblate (Polish)
Currently translated at 98.1% (489 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-12-12 17:29:08 +01:00
J-Jamet
bd0b5b0954 Fix exception message 2020-12-12 14:53:23 +01:00
J-Jamet
7dc93604ad Refactoring AdvancedUnlockManager.kt 2020-12-12 14:13:13 +01:00
J-Jamet
0ab22698a6 Refactoring classes 2020-12-11 15:15:52 +01:00
J-Jamet
c885ce7aaf Refactoring of advanced unlock 2020-12-11 13:36:51 +01:00
J-Jamet
92d1a7b901 Fix string 2020-12-11 11:11:48 +01:00
J-Jamet
6119054b45 Upgrade to version 2.9.5 2020-12-11 11:03:00 +01:00
Eric
e7aed72398 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-12-11 01:55:56 +01:00
solokot
cee7fa50f5 Translated using Weblate (Russian)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-12-11 01:55:55 +01:00
Stephan Paternotte
39a38bb223 Translated using Weblate (Dutch)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-12-11 01:55:55 +01:00
Oliver Cervera
7159a993db Translated using Weblate (Italian)
Currently translated at 99.7% (497 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-12-11 01:55:54 +01:00
J-Jamet
23933e80e3 Merge tag '2.9.4' into develop
2.9.4
2020-12-10 22:27:38 +01:00
42 changed files with 1215 additions and 846 deletions

View File

@@ -1,3 +1,8 @@
KeePassDX(2.9.5)
* Unlock database by device credentials (PIN/Password/Pattern) with Android M+ #102 #152 #811
* Prevent auto switch back to previous keyboard if otp field exists #814
* Fix timeout reset #817
KeePassDX(2.9.4)
* Fix small bugs #812
* Argon2ID implementation #791

View File

@@ -12,8 +12,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 30
versionCode = 48
versionName = "2.9.4"
versionCode = 49
versionName = "2.9.5"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -40,6 +40,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
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.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
@@ -133,7 +134,7 @@ class EntryActivity : LockingActivity() {
}
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
// Init the clipboard helper
clipboardHelper = ClipboardHelper(this)

View File

@@ -48,6 +48,7 @@ import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Compani
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage
@@ -134,7 +135,7 @@ class EntryEditActivity : LockingActivity(),
}
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java))

View File

@@ -37,6 +37,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment
@@ -148,6 +149,8 @@ class EntryEditFragment: StylishFragment() {
iconColor = taIconColor?.getColor(0, Color.WHITE) ?: Color.WHITE
taIconColor?.recycle()
rootView?.resetAppTimeoutWhenViewFocusedOrChanged(requireContext())
// Retrieve the new entry after an orientation change
if (arguments?.containsKey(KEY_TEMP_ENTRY_INFO) == true)
mEntryInfo = arguments?.getParcelable(KEY_TEMP_ENTRY_INFO) ?: mEntryInfo

View File

@@ -434,8 +434,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
when (item.itemId) {
android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
}
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
return super.onOptionsItemSelected(item)
}
companion object {

View File

@@ -50,6 +50,7 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database
@@ -153,7 +154,7 @@ class GroupActivity : LockingActivity(),
taTextColor.recycle()
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(rootContainerView)
rootContainerView?.resetAppTimeoutWhenViewFocusedOrChanged(this)
// Retrieve elements after an orientation change
if (savedInstanceState != null) {

View File

@@ -37,8 +37,8 @@ import android.widget.*
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat
import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
@@ -50,8 +50,7 @@ import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
@@ -69,14 +68,13 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException
open class PasswordActivity : SpecialModeActivity() {
open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.BuilderListener {
// Views
private var toolbar: Toolbar? = null
@@ -86,9 +84,8 @@ open class PasswordActivity : SpecialModeActivity() {
private var confirmButtonView: Button? = null
private var checkboxPasswordView: CompoundButton? = null
private var checkboxKeyFileView: CompoundButton? = null
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
private var infoContainerView: ViewGroup? = null
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
@@ -114,7 +111,6 @@ open class PasswordActivity : SpecialModeActivity() {
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
private var mAllowAutoOpenBiometricPrompt: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
@@ -134,7 +130,6 @@ open class PasswordActivity : SpecialModeActivity() {
keyFileSelectionView = findViewById(R.id.keyfile_selection)
checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
advancedUnlockInfoView = findViewById(R.id.biometric_info)
infoContainerView = findViewById(R.id.activity_password_info_container)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
@@ -161,10 +156,6 @@ open class PasswordActivity : SpecialModeActivity() {
}
})
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
enableOrNotTheConfirmationButton()
}
// If is a view intent
getUriFromIntent(intent)
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
@@ -174,6 +165,24 @@ open class PasswordActivity : SpecialModeActivity() {
mAllowAutoOpenBiometricPrompt = savedInstanceState.getBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT)
}
// Init Biometric elements
advancedUnlockFragment = supportFragmentManager
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
if (advancedUnlockFragment == null) {
advancedUnlockFragment = AdvancedUnlockFragment()
supportFragmentManager.commit {
replace(R.id.fragment_advanced_unlock_container_view,
advancedUnlockFragment!!,
UNLOCK_FRAGMENT_TAG)
}
}
// Listen password checkbox to init advanced unlock and confirmation button
checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
advancedUnlockFragment?.checkUnlockAvailability()
enableOrNotTheConfirmationButton()
}
// Observe if default database
databaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
mDefaultDatabase = isDefaultDatabase
@@ -207,12 +216,7 @@ open class PasswordActivity : SpecialModeActivity() {
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isAdvancedUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initAdvancedUnlockMode()
}
}
advancedUnlockFragment?.initAdvancedUnlockMode()
if (result.isSuccess) {
mDatabaseKeyFileUri = null
@@ -320,6 +324,33 @@ open class PasswordActivity : SpecialModeActivity() {
finish()
}
override fun retrieveCredentialForEncryption(): String {
return passwordView?.text?.toString() ?: ""
}
override fun conditionToStoreCredential(): Boolean {
return checkboxPasswordView?.isChecked == true
}
override fun onCredentialEncrypted(databaseUri: Uri,
encryptedCredential: String,
ivSpec: String) {
// Load the database if password is registered with biometric
verifyCheckboxesAndLoadDatabase(
CipherDatabaseEntity(
databaseUri.toString(),
encryptedCredential,
ivSpec)
)
}
override fun onCredentialDecrypted(databaseUri: Uri,
decryptedCredential: String) {
// Load the database if password is retrieve from biometric
// Retrieve from biometric
verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential)
}
private val onEditorActionListener = object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == IME_ACTION_DONE) {
@@ -386,48 +417,9 @@ open class PasswordActivity : SpecialModeActivity() {
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else {
// Init Biometric elements
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isAdvancedUnlockEnable(this)) {
if (advancedUnlockedManager == null
&& databaseFileUri != null) {
advancedUnlockedManager = AdvancedUnlockedManager(this,
databaseFileUri,
advancedUnlockInfoView,
checkboxPasswordView,
enableButtonOnCheckedChangeListener,
passwordView,
{ passwordEncrypted, ivSpec ->
// Load the database if password is registered with biometric
if (passwordEncrypted != null && ivSpec != null) {
verifyCheckboxesAndLoadDatabase(
CipherDatabaseEntity(
databaseFileUri.toString(),
passwordEncrypted,
ivSpec)
)
}
},
{ passwordDecrypted ->
// Load the database if password is retrieve from biometric
passwordDecrypted?.let {
// Retrieve from biometric
verifyKeyFileCheckboxAndLoadDatabase(it)
}
})
}
advancedUnlockedManager?.isBiometricPromptAutoOpenEnable =
mAllowAutoOpenBiometricPrompt && mProgressDatabaseTaskProvider?.isBinded() != true
advancedUnlockedManager?.checkBiometricAvailability()
} else {
advancedUnlockInfoView?.visibility = View.GONE
advancedUnlockedManager?.destroy()
advancedUnlockedManager = null
}
}
if (advancedUnlockedManager == null) {
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
}
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
advancedUnlockFragment?.loadDatabase(databaseFileUri,
mAllowAutoOpenBiometricPrompt
&& mProgressDatabaseTaskProvider?.isBinded() != true)
}
enableOrNotTheConfirmationButton()
@@ -479,11 +471,6 @@ open class PasswordActivity : SpecialModeActivity() {
override fun onPause() {
mProgressDatabaseTaskProvider?.unregisterProgressTask()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.destroy()
advancedUnlockedManager = null
}
// Reinit locking activity UI variable
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true
@@ -592,11 +579,6 @@ open class PasswordActivity : SpecialModeActivity() {
MenuUtil.defaultMenuInflater(inflater, menu)
}
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
}
super.onCreateOptionsMenu(menu)
launchEducation(menu)
@@ -672,21 +654,14 @@ open class PasswordActivity : SpecialModeActivity() {
performedNextEducation(passwordActivityEducation, menu)
})
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(this)
PreferencesUtil.isAdvancedUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.visibility == View.VISIBLE
&& advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
})
}
advancedUnlockFragment?.performEducation(passwordActivityEducation,
readOnlyEducationPerformed,
{
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
})
}
}
@@ -708,10 +683,7 @@ open class PasswordActivity : SpecialModeActivity() {
readOnly = !readOnly
changeOpenFileReadIcon(item)
}
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.deleteEncryptedDatabaseKey()
}
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
}
return super.onOptionsItemSelected(item)
@@ -725,6 +697,9 @@ open class PasswordActivity : SpecialModeActivity() {
mAllowAutoOpenBiometricPrompt = false
// To get device credential unlock result
advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data)
// To get entry in result
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
@@ -758,6 +733,8 @@ open class PasswordActivity : SpecialModeActivity() {
private val TAG = PasswordActivity::class.java.name
private const val UNLOCK_FRAGMENT_TAG = "UNLOCK_FRAGMENT_TAG"
private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW"

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.activities.lock
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.MotionEvent
@@ -163,35 +164,6 @@ abstract class LockingActivity : SpecialModeActivity() {
sendBroadcast(Intent(LOCK_ACTION))
}
/**
* To reset the app timeout when a view is focused or changed
*/
@SuppressLint("ClickableViewAccessibility")
protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View?) {
views.forEach {
it?.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Log.d(TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
}
}
false
}
it?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
// Log.d(TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
}
}
if (it is ViewGroup) {
for (i in 0..it.childCount) {
resetAppTimeoutWhenViewFocusedOrChanged(it.getChildAt(i))
}
}
}
}
override fun onBackPressed() {
if (mTimeoutEnable) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
@@ -204,7 +176,7 @@ abstract class LockingActivity : SpecialModeActivity() {
companion object {
private const val TAG = "LockingActivity"
const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
@@ -215,3 +187,28 @@ abstract class LockingActivity : SpecialModeActivity() {
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
}
}
/**
* To reset the app timeout when a view is focused or changed
*/
@SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context) {
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
//Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context)
}
}
false
}
setOnFocusChangeListener { _, _ ->
//Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context)
}
}
}

View File

@@ -0,0 +1,10 @@
package com.kunzisoft.keepass.biometric
import androidx.annotation.StringRes
import javax.crypto.Cipher
data class AdvancedUnlockCryptoPrompt(var cipher: Cipher,
@StringRes var promptTitleId: Int,
@StringRes var promptDescriptionId: Int? = null,
var isDeviceCredentialOperation: Boolean,
var isBiometricOperation: Boolean)

View File

@@ -0,0 +1,620 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.*
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import com.getkeepsafe.taptargetview.TapTargetView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.exception.IODatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
private var mBuilderListener: BuilderListener? = null
private var mAdvancedUnlockEnabled = false
private var mAutoOpenPromptEnabled = false
private var advancedUnlockManager: AdvancedUnlockManager? = null
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE
private var mAdvancedUnlockInfoView: AdvancedUnlockInfoView? = null
var databaseFileUri: Uri? = null
private set
/**
* Manage setting to auto open biometric prompt
*/
private var mAutoOpenPrompt: Boolean = false
get() {
return field && mAutoOpenPromptEnabled
}
// Variable to check if the prompt can be open (if the right activity is currently shown)
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
private var allowOpenBiometricPrompt = false
private lateinit var cipherDatabaseAction : CipherDatabaseAction
private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null
// Only to fix multiple fingerprint menu #332
private var mAllowAdvancedUnlockMenu = false
private var mAddBiometricMenuInProgress = false
// Only keep connection when we request a device credential activity
private var keepConnection = false
override fun onAttach(context: Context) {
super.onAttach(context)
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context)
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBuilderListener = context as BuilderListener
}
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + BuilderListener::class.java.name)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
setHasOptionsMenu(true)
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_advanced_unlock, container, false)
mAdvancedUnlockInfoView = rootView.findViewById(R.id.advanced_unlock_view)
return rootView
}
private data class ActivityResult(var requestCode: Int, var resultCode: Int, var data: Intent?)
private var activityResult: ActivityResult? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// To wait resume
activityResult = ActivityResult(requestCode, resultCode, data)
keepConnection = false
super.onActivityResult(requestCode, resultCode, data)
}
override fun onResume() {
super.onResume()
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext())
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(requireContext())
keepConnection = false
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
if (mAllowAdvancedUnlockMenu)
inflater.inflate(R.menu.advanced_unlock, menu)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEncryptedDatabaseKey()
}
}
return super.onOptionsItemSelected(item)
}
fun loadDatabase(databaseUri: Uri?, autoOpenPrompt: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// To get device credential unlock result, only if same database uri
if (databaseUri != null
&& mAdvancedUnlockEnabled) {
activityResult?.let {
if (databaseUri == databaseFileUri) {
advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode)
} else {
disconnect()
}
} ?: run {
connect(databaseUri)
this.mAutoOpenPrompt = autoOpenPrompt
}
} else {
disconnect()
}
activityResult = null
}
}
/**
* Check unlock availability and change the current mode depending of device's state
*/
fun checkUnlockAvailability() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
allowOpenBiometricPrompt = true
if (PreferencesUtil.isBiometricUnlockEnable(requireContext())) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
// biometric not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
} else {
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
} else {
selectMode()
}
}
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(requireContext())) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
if (AdvancedUnlockManager.isDeviceSecure(requireContext())) {
selectMode()
} else {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun selectMode() {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
advancedUnlockManager = AdvancedUnlockManager { requireActivity() }
// callback for fingerprint findings
advancedUnlockManager?.advancedUnlockCallback = this
}
// Recheck to change the mode
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else {
if (mBuilderListener?.conditionToStoreCredential() == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode(if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
} ?: throw IODatabaseException()
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun toggleMode(newBiometricMode: Mode) {
if (newBiometricMode != biometricMode) {
biometricMode = newBiometricMode
initAdvancedUnlockMode()
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initNotAvailable() {
showViews(false)
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
}
@RequiresApi(Build.VERSION_CODES.M)
private fun openBiometricSetting() {
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
requireContext().startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initSecurityUpdateRequired() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
openBiometricSetting()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initNotConfigured() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("")
openBiometricSetting()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initKeyManagerNotAvailable() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
openBiometricSetting()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initWaitData() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("")
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
requireContext().getString(R.string.credential_before_click_advanced_unlock_button))
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
requireActivity().runOnUiThread {
if (allowOpenBiometricPrompt) {
if (cryptoPrompt.isDeviceCredentialOperation)
keepConnection = true
try {
advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt)
} catch (e: Exception) {
Log.e(TAG, "Unable to open advanced unlock prompt", e)
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initEncryptData() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential)
setAdvancedUnlockedMessageView("")
advancedUnlockManager?.initEncryptData { cryptoPrompt ->
// Set listener to open the biometric dialog and save credential
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
openAdvancedUnlockPrompt(cryptoPrompt)
}
} ?: throw Exception("AdvancedUnlockHelper not initialized")
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initDecryptData() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_unlock_database)
setAdvancedUnlockedMessageView("")
advancedUnlockManager?.let { unlockHelper ->
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
cipherDatabase?.let {
unlockHelper.initDecryptData(it.specParameters) { cryptoPrompt ->
// Set listener to open the biometric dialog and check credential
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
openAdvancedUnlockPrompt(cryptoPrompt)
}
// Auto open the biometric prompt
if (mAutoOpenPrompt) {
mAutoOpenPrompt = false
openAdvancedUnlockPrompt(cryptoPrompt)
}
}
} ?: deleteEncryptedDatabaseKey()
}
} ?: throw IODatabaseException()
} ?: throw Exception("AdvancedUnlockHelper 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()
}
invalidateBiometricMenu()
}
}
private fun invalidateBiometricMenu() {
// Show fingerprint key deletion
if (!mAddBiometricMenuInProgress) {
mAddBiometricMenuInProgress = true
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
mAllowAdvancedUnlockMenu = containsCipher
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
mAddBiometricMenuInProgress = false
requireActivity().invalidateOptionsMenu()
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
fun connect(databaseUri: Uri) {
showViews(true)
this.databaseFileUri = databaseUri
cipherDatabaseListener = object: CipherDatabaseAction.DatabaseListener {
override fun onDatabaseCleared() {
deleteEncryptedDatabaseKey()
}
}
cipherDatabaseAction.apply {
reloadPreferences()
cipherDatabaseListener?.let {
registerDatabaseListener(it)
}
}
checkUnlockAvailability()
}
@RequiresApi(Build.VERSION_CODES.M)
fun disconnect(hideViews: Boolean = true,
closePrompt: Boolean = true) {
this.databaseFileUri = null
// Close the biometric prompt
allowOpenBiometricPrompt = false
if (closePrompt)
advancedUnlockManager?.closeBiometricPrompt()
cipherDatabaseListener?.let {
cipherDatabaseAction.unregisterDatabaseListener(it)
}
biometricMode = Mode.BIOMETRIC_UNAVAILABLE
if (hideViews) {
showViews(false)
}
}
@RequiresApi(Build.VERSION_CODES.M)
fun deleteEncryptedDatabaseKey() {
allowOpenBiometricPrompt = false
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
advancedUnlockManager?.closeBiometricPrompt()
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
checkUnlockAvailability()
}
}
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
requireActivity().runOnUiThread {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedMessageView(errString.toString())
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationFailed() {
requireActivity().runOnUiThread {
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationSucceeded() {
requireActivity().runOnUiThread {
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> {
}
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
}
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> {
}
Mode.KEY_MANAGER_UNAVAILABLE -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
advancedUnlockManager?.encryptData(credential)
}
AdvancedUnlockNotificationService.startServiceForTimeout(requireContext())
}
Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
cipherDatabase?.encryptedValue?.let { value ->
advancedUnlockManager?.decryptData(value)
} ?: deleteEncryptedDatabaseKey()
}
} ?: throw IODatabaseException()
}
}
}
}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec)
}
}
override fun handleDecryptedResult(decryptedValue: String) {
// Load database directly with password retrieve
databaseFileUri?.let {
mBuilderListener?.onCredentialDecrypted(it, decryptedValue)
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onInvalidKeyException(e: Exception) {
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
}
override fun onGenericException(e: Exception) {
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
setAdvancedUnlockedMessageView(errorMessage)
}
private fun showViews(show: Boolean) {
requireActivity().runOnUiThread {
mAdvancedUnlockInfoView?.visibility = if (show)
View.VISIBLE
else {
View.GONE
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedTitleView(textId: Int) {
requireActivity().runOnUiThread {
mAdvancedUnlockInfoView?.setTitle(textId)
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedMessageView(textId: Int) {
requireActivity().runOnUiThread {
mAdvancedUnlockInfoView?.setMessage(textId)
}
}
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
requireActivity().runOnUiThread {
mAdvancedUnlockInfoView?.message = text
}
}
fun performEducation(passwordActivityEducation: PasswordActivityEducation,
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)
}
}
enum class Mode {
BIOMETRIC_UNAVAILABLE,
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED,
KEY_MANAGER_UNAVAILABLE,
WAIT_CREDENTIAL,
STORE_CREDENTIAL,
EXTRACT_CREDENTIAL
}
interface BuilderListener {
fun retrieveCredentialForEncryption(): String
fun conditionToStoreCredential(): Boolean
fun onCredentialEncrypted(databaseUri: Uri, encryptedCredential: String, ivSpec: String)
fun onCredentialDecrypted(databaseUri: Uri, decryptedCredential: String)
}
override fun onPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!keepConnection) {
// If close prompt, bug "user not authenticated in Android R"
disconnect(false)
advancedUnlockManager = null
}
}
super.onPause()
}
override fun onDestroyView() {
mAdvancedUnlockInfoView = null
super.onDestroyView()
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
disconnect()
advancedUnlockManager = null
mBuilderListener = null
}
super.onDestroy()
}
override fun onDetach() {
mBuilderListener = null
super.onDetach()
}
companion object {
private val TAG = AdvancedUnlockFragment::class.java.name
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -19,6 +19,7 @@
*/
package com.kunzisoft.keepass.biometric
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.os.Build
@@ -31,6 +32,7 @@ import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.*
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -44,48 +46,74 @@ import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
@RequiresApi(api = Build.VERSION_CODES.M)
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private var biometricPrompt: BiometricPrompt? = null
class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity) {
private var keyStore: KeyStore? = null
private var keyGenerator: KeyGenerator? = null
private var cipher: Cipher? = null
private var keyguardManager: KeyguardManager? = null
private var cryptoObject: BiometricPrompt.CryptoObject? = null
private var biometricPrompt: BiometricPrompt? = null
private var authenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
advancedUnlockCallback?.onAuthenticationSucceeded()
}
override fun onAuthenticationFailed() {
advancedUnlockCallback?.onAuthenticationFailed()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
advancedUnlockCallback?.onAuthenticationError(errorCode, errString)
}
}
var advancedUnlockCallback: AdvancedUnlockCallback? = null
private var isKeyManagerInit = false
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
var biometricUnlockCallback: BiometricUnlockCallback? = null
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(context)
private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(retrieveContext())
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext())
val isKeyManagerInitialized: Boolean
get() {
if (!isKeyManagerInit) {
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
advancedUnlockCallback?.onGenericException(Exception("Biometric not initialized"))
}
return isKeyManagerInit
}
private fun isBiometricOperation(): Boolean {
return biometricUnlockEnable || isDeviceCredentialBiometricOperation()
}
// Since Android 30, device credential is also a biometric operation
private fun isDeviceCredentialOperation(): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R
&& deviceCredentialUnlockEnable
}
private fun isDeviceCredentialBiometricOperation(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& deviceCredentialUnlockEnable
}
init {
if (allowInitKeyStore(context)) {
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
if (isDeviceSecure(retrieveContext())
&& (biometricUnlockEnable || deviceCredentialUnlockEnable)) {
try {
this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE)
this.keyGenerator = KeyGenerator.getInstance(BIOMETRIC_KEY_ALGORITHM, BIOMETRIC_KEYSTORE)
this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE)
this.keyGenerator = KeyGenerator.getInstance(ADVANCED_UNLOCK_KEY_ALGORITHM, ADVANCED_UNLOCK_KEYSTORE)
this.cipher = Cipher.getInstance(
BIOMETRIC_KEY_ALGORITHM + "/"
+ BIOMETRIC_BLOCKS_MODES + "/"
+ BIOMETRIC_ENCRYPTION_PADDING)
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
ADVANCED_UNLOCK_KEY_ALGORITHM + "/"
+ ADVANCED_UNLOCK_BLOCKS_MODES + "/"
+ ADVANCED_UNLOCK_ENCRYPTION_PADDING)
isKeyManagerInit = (keyStore != null
&& keyGenerator != null
&& cipher != null)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize the keystore", e)
isKeyManagerInit = false
biometricUnlockCallback?.onBiometricException(e)
advancedUnlockCallback?.onGenericException(e)
}
} else {
// really not much to do when no fingerprint support found
@@ -103,22 +131,20 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
keyStore.load(null)
try {
if (!keyStore.containsAlias(BIOMETRIC_KEYSTORE_KEY)) {
if (!keyStore.containsAlias(ADVANCED_UNLOCK_KEYSTORE_KEY)) {
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
keyGenerator?.init(
KeyGenParameterSpec.Builder(
BIOMETRIC_KEYSTORE_KEY,
ADVANCED_UNLOCK_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
// Require the user to authenticate with a fingerprint to authorize every use
// of the key
.setUserAuthenticationRequired(true)
// of the key, don't use it for device credential because it's the user authentication
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& deviceCredentialUnlockEnable) {
setUserAuthenticationParameters(0, KeyProperties.AUTH_DEVICE_CREDENTIAL)
if (biometricUnlockEnable) {
setUserAuthenticationRequired(true)
}
}
.build())
@@ -126,56 +152,46 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
}
} catch (e: Exception) {
Log.e(TAG, "Unable to create a key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
advancedUnlockCallback?.onGenericException(e)
}
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey?
return keyStore.getKey(ADVANCED_UNLOCK_KEYSTORE_KEY, null) as SecretKey?
}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve the key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
advancedUnlockCallback?.onGenericException(e)
}
return null
}
fun initEncryptData(actionIfCypherInit
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
if (!isKeyManagerInitialized) {
return
}
try {
// TODO if (keyguardManager?.isDeviceSecure == true) {
getSecretKey()?.let { secretKey ->
cipher?.init(Cipher.ENCRYPT_MODE, secretKey)
cipher?.let { cipher ->
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
initBiometricPrompt()
val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.advanced_unlock_prompt_store_credential_title))
setDescription(context.getString(R.string.advanced_unlock_prompt_store_credential_message))
setConfirmationRequired(true)
if (deviceCredentialUnlockEnable) {
setAllowedAuthenticators(DEVICE_CREDENTIAL)
} else {
setNegativeButtonText(context.getString(android.R.string.cancel))
}
}.build()
actionIfCypherInit.invoke(biometricPrompt,
cryptoObject,
promptInfoStoreCredential)
actionIfCypherInit.invoke(
AdvancedUnlockCryptoPrompt(
cipher,
R.string.advanced_unlock_prompt_store_credential_title,
R.string.advanced_unlock_prompt_store_credential_message,
isDeviceCredentialOperation(), isBiometricOperation())
)
}
}
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
advancedUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize encrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
advancedUnlockCallback?.onGenericException(e)
}
}
@@ -190,57 +206,46 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
// passes updated iv spec on to callback so this can be stored for decryption
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
advancedUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
}
} catch (e: Exception) {
val exception = Exception(context.getString(R.string.keystore_not_accessible), e)
Log.e(TAG, "Unable to encrypt data", e)
biometricUnlockCallback?.onBiometricException(exception)
advancedUnlockCallback?.onGenericException(e)
}
}
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
if (!isKeyManagerInitialized) {
return
}
try {
// TODO if (keyguardManager?.isDeviceSecure == true) {
// important to restore spec here that was used for decryption
val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP)
val spec = IvParameterSpec(iv)
getSecretKey()?.let { secretKey ->
cipher?.init(Cipher.DECRYPT_MODE, secretKey, spec)
cipher?.let { cipher ->
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
initBiometricPrompt()
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.advanced_unlock_prompt_extract_credential_title))
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
setConfirmationRequired(false)
if (deviceCredentialUnlockEnable) {
setAllowedAuthenticators(DEVICE_CREDENTIAL)
} else {
setNegativeButtonText(context.getString(android.R.string.cancel))
}
}.build()
actionIfCypherInit.invoke(biometricPrompt,
cryptoObject,
promptInfoExtractCredential)
actionIfCypherInit.invoke(
AdvancedUnlockCryptoPrompt(
cipher,
R.string.advanced_unlock_prompt_extract_credential_title,
null,
isDeviceCredentialOperation(), isBiometricOperation())
)
}
}
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
deleteKeystoreKey()
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize decrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
advancedUnlockCallback?.onGenericException(e)
}
}
@@ -252,33 +257,73 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
// actual decryption here
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
cipher?.doFinal(encrypted)?.let { decrypted ->
biometricUnlockCallback?.handleDecryptedResult(String(decrypted))
advancedUnlockCallback?.handleDecryptedResult(String(decrypted))
}
} catch (badPaddingException: BadPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException)
biometricUnlockCallback?.onInvalidKeyException(badPaddingException)
advancedUnlockCallback?.onInvalidKeyException(badPaddingException)
} catch (e: Exception) {
val exception = Exception(context.getString(R.string.keystore_not_accessible), e)
Log.e(TAG, "Unable to decrypt data", exception)
biometricUnlockCallback?.onBiometricException(exception)
Log.e(TAG, "Unable to decrypt data", e)
advancedUnlockCallback?.onGenericException(e)
}
}
fun deleteKeystoreKey() {
try {
keyStore?.load(null)
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY)
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
} catch (e: Exception) {
Log.e(TAG, "Unable to delete entry key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
advancedUnlockCallback?.onGenericException(e)
}
}
@Suppress("DEPRECATION")
@Synchronized
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
// Init advanced unlock prompt
if (biometricPrompt == null) {
biometricPrompt = BiometricPrompt(retrieveContext(),
Executors.newSingleThreadExecutor(),
authenticationCallback)
}
val promptTitle = retrieveContext().getString(cryptoPrompt.promptTitleId)
val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId ->
retrieveContext().getString(descriptionId)
} ?: ""
if (cryptoPrompt.isBiometricOperation) {
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(promptTitle)
if (promptDescription.isNotEmpty())
setDescription(promptDescription)
setConfirmationRequired(false)
if (isDeviceCredentialBiometricOperation()) {
setAllowedAuthenticators(DEVICE_CREDENTIAL)
} else {
setNegativeButtonText(retrieveContext().getString(android.R.string.cancel))
}
}.build()
biometricPrompt?.authenticate(
promptInfoExtractCredential,
BiometricPrompt.CryptoObject(cryptoPrompt.cipher))
}
else if (cryptoPrompt.isDeviceCredentialOperation) {
val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java)
retrieveContext().startActivityForResult(
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription),
REQUEST_DEVICE_CREDENTIAL)
}
}
@Synchronized
fun initBiometricPrompt() {
if (biometricPrompt == null) {
authenticationCallback?.let {
biometricPrompt = BiometricPrompt(context, Executors.newSingleThreadExecutor(), it)
fun onActivityResult(requestCode: Int, resultCode: Int) {
if (requestCode == REQUEST_DEVICE_CREDENTIAL) {
if (resultCode == Activity.RESULT_OK) {
advancedUnlockCallback?.onAuthenticationSucceeded()
} else {
advancedUnlockCallback?.onAuthenticationFailed()
}
}
}
@@ -287,25 +332,30 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
biometricPrompt?.cancelAuthentication()
}
interface BiometricUnlockErrorCallback {
interface AdvancedUnlockErrorCallback {
fun onInvalidKeyException(e: Exception)
fun onBiometricException(e: Exception)
fun onGenericException(e: Exception)
}
interface BiometricUnlockCallback : BiometricUnlockErrorCallback {
interface AdvancedUnlockCallback : AdvancedUnlockErrorCallback {
fun onAuthenticationSucceeded()
fun onAuthenticationFailed()
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
fun handleEncryptedResult(encryptedValue: String, ivSpec: String)
fun handleDecryptedResult(decryptedValue: String)
}
companion object {
private val TAG = BiometricUnlockDatabaseHelper::class.java.name
private val TAG = AdvancedUnlockManager::class.java.name
private const val BIOMETRIC_KEYSTORE = "AndroidKeyStore"
private const val BIOMETRIC_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
private const val BIOMETRIC_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
private const val BIOMETRIC_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
private const val BIOMETRIC_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
private const val ADVANCED_UNLOCK_KEYSTORE = "AndroidKeyStore"
private const val ADVANCED_UNLOCK_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
private const val ADVANCED_UNLOCK_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
private const val REQUEST_DEVICE_CREDENTIAL = 556
@RequiresApi(api = Build.VERSION_CODES.M)
fun canAuthenticate(context: Context): Int {
@@ -337,11 +387,9 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
}
@RequiresApi(api = Build.VERSION_CODES.M)
fun allowInitKeyStore(context: Context): Boolean {
val biometricCanAuthenticate = canAuthenticate(context)
return ( biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
)
fun isDeviceSecure(context: Context): Boolean {
val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
return keyguardManager?.isDeviceSecure ?: false
}
@RequiresApi(api = Build.VERSION_CODES.M)
@@ -365,36 +413,48 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
)
}
@RequiresApi(api = Build.VERSION_CODES.R)
@RequiresApi(api = Build.VERSION_CODES.M)
fun deviceCredentialUnlockSupported(context: Context): Boolean {
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ContextCompat.getSystemService(context, KeyguardManager::class.java)?.apply {
return isDeviceSecure
}
}
return false
}
/**
* Remove entry key in keystore
*/
@RequiresApi(api = Build.VERSION_CODES.M)
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
biometricCallback: BiometricUnlockErrorCallback) {
BiometricUnlockDatabaseHelper(context).apply {
biometricUnlockCallback = object : BiometricUnlockCallback {
fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity,
advancedCallback: AdvancedUnlockErrorCallback) {
AdvancedUnlockManager{ fragmentActivity }.apply {
advancedUnlockCallback = object : AdvancedUnlockCallback {
override fun onAuthenticationSucceeded() {}
override fun onAuthenticationFailed() {}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleDecryptedResult(decryptedValue: String) {}
override fun onInvalidKeyException(e: Exception) {
biometricCallback.onInvalidKeyException(e)
advancedCallback.onInvalidKeyException(e)
}
override fun onBiometricException(e: Exception) {
biometricCallback.onBiometricException(e)
override fun onGenericException(e: Exception) {
advancedCallback.onGenericException(e)
}
}
deleteKeystoreKey()

View File

@@ -1,422 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.widget.CompoundButton
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
@RequiresApi(api = Build.VERSION_CODES.M)
class AdvancedUnlockedManager(var context: FragmentActivity,
var databaseFileUri: Uri,
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
private var checkboxPasswordView: CompoundButton?,
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
var passwordView: TextView?,
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE
// Only to fix multiple fingerprint menu #332
private var mAllowAdvancedUnlockMenu = false
private var mAddBiometricMenuInProgress = false
/**
* Manage setting to auto open biometric prompt
*/
private var biometricPromptAutoOpenPreference = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
var isBiometricPromptAutoOpenEnable: Boolean = false
get() {
return field && biometricPromptAutoOpenPreference
}
// Variable to check if the prompt can be open (if the right activity is currently shown)
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
private var allowOpenBiometricPrompt = false
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
private val cipherDatabaseListener = object: CipherDatabaseAction.DatabaseListener {
override fun onDatabaseCleared() {
deleteEncryptedDatabaseKey()
}
}
init {
// Add a check listener to change fingerprint mode
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
checkBiometricAvailability()
// Add old listener to enable the button, only be call here because of onCheckedChange bug
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
}
cipherDatabaseAction.apply {
reloadPreferences()
registerDatabaseListener(cipherDatabaseListener)
}
}
/**
* Check biometric availability and change the current mode depending of device's state
*/
fun checkBiometricAvailability() {
if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
advancedUnlockInfoView?.setIconResource(R.drawable.bolt)
} else if (PreferencesUtil.isBiometricUnlockEnable(context)) {
advancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
}
// biometric not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(context)
allowOpenBiometricPrompt = true
if (!PreferencesUtil.isAdvancedUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED){
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
} else {
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
} else {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
}
// Recheck to change the mode
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else {
if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode(if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
}
}
}
}
}
private fun toggleMode(newBiometricMode: Mode) {
if (newBiometricMode != biometricMode) {
biometricMode = newBiometricMode
initAdvancedUnlockMode()
}
}
private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence) {
context.runOnUiThread {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedMessageView(errString.toString())
}
}
override fun onAuthenticationFailed() {
context.runOnUiThread {
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
}
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
context.runOnUiThread {
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> {
}
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
}
Mode.BIOMETRIC_NOT_CONFIGURED -> {
}
Mode.KEY_MANAGER_UNAVAILABLE -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
AdvancedUnlockNotificationService.startServiceForTimeout(context)
}
Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences
cipherDatabaseAction.getCipherDatabase(databaseFileUri) { cipherDatabase ->
cipherDatabase?.encryptedValue?.let { value ->
biometricUnlockDatabaseHelper?.decryptData(value)
} ?: deleteEncryptedDatabaseKey()
}
}
}
}
}
}
private fun initNotAvailable() {
showFingerPrintViews(false)
advancedUnlockInfoView?.setIconViewClickListener(false, null)
}
@Suppress("DEPRECATION")
private fun openBiometricSetting() {
advancedUnlockInfoView?.setIconViewClickListener(false) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
context.startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
private fun initSecurityUpdateRequired() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
openBiometricSetting()
}
private fun initNotConfigured() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("")
openBiometricSetting()
}
private fun initKeyManagerNotAvailable() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
openBiometricSetting()
}
private fun initWaitData() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
biometricAuthenticationCallback.onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
context.getString(R.string.credential_before_click_advanced_unlock_button))
}
}
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo) {
context.runOnUiThread {
if (allowOpenBiometricPrompt) {
if (biometricPrompt != null) {
if (cryptoObject != null) {
biometricPrompt.authenticate(promptInfo, cryptoObject)
} else {
setAdvancedUnlockedTitleView(R.string.crypto_object_not_initialized)
}
} else {
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
}
}
}
}
private fun initEncryptData() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential)
setAdvancedUnlockedMessageView("")
biometricUnlockDatabaseHelper?.initEncryptData { biometricPrompt, cryptoObject, promptInfo ->
// Set listener to open the biometric dialog and save credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
}
}
}
private fun initDecryptData() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_unlock_database)
setAdvancedUnlockedMessageView("")
if (biometricUnlockDatabaseHelper != null) {
cipherDatabaseAction.getCipherDatabase(databaseFileUri) { cipherDatabase ->
cipherDatabase?.let {
biometricUnlockDatabaseHelper?.initDecryptData(it.specParameters) { biometricPrompt, cryptoObject, promptInfo ->
// Set listener to open the biometric dialog and check credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
}
// Auto open the biometric prompt
if (isBiometricPromptAutoOpenEnable) {
isBiometricPromptAutoOpenEnable = false
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
}
}
} ?: deleteEncryptedDatabaseKey()
}
}
}
@Synchronized
fun initAdvancedUnlockMode() {
mAllowAdvancedUnlockMenu = false
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
}
invalidateBiometricMenu()
}
private fun invalidateBiometricMenu() {
// Show fingerprint key deletion
if (!mAddBiometricMenuInProgress) {
mAddBiometricMenuInProgress = true
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
mAllowAdvancedUnlockMenu = containsCipher
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
mAddBiometricMenuInProgress = false
context.invalidateOptionsMenu()
}
}
}
fun destroy() {
// Close the biometric prompt
allowOpenBiometricPrompt = false
biometricUnlockDatabaseHelper?.closeBiometricPrompt()
// Restore the checked listener
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
cipherDatabaseAction.unregisterDatabaseListener(cipherDatabaseListener)
}
fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) {
if (mAllowAdvancedUnlockMenu)
menuInflater.inflate(R.menu.advanced_unlock, menu)
}
fun deleteEncryptedDatabaseKey() {
allowOpenBiometricPrompt = false
advancedUnlockInfoView?.setIconViewClickListener(false, null)
biometricUnlockDatabaseHelper?.closeBiometricPrompt()
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri) {
checkBiometricAvailability()
}
}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec)
}
override fun handleDecryptedResult(decryptedValue: String) {
// Load database directly with password retrieve
loadDatabaseAfterRetrieveCredentials.invoke(decryptedValue)
}
override fun onInvalidKeyException(e: Exception) {
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
}
override fun onBiometricException(e: Exception) {
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
setAdvancedUnlockedMessageView(errorMessage)
}
private fun showFingerPrintViews(show: Boolean) {
context.runOnUiThread {
advancedUnlockInfoView?.visibility = if (show) View.VISIBLE else View.GONE
}
}
private fun setAdvancedUnlockedTitleView(textId: Int) {
context.runOnUiThread {
advancedUnlockInfoView?.setTitle(textId)
}
}
private fun setAdvancedUnlockedMessageView(textId: Int) {
context.runOnUiThread {
advancedUnlockInfoView?.setMessage(textId)
}
}
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
context.runOnUiThread {
advancedUnlockInfoView?.message = text
}
}
enum class Mode {
BIOMETRIC_UNAVAILABLE,
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_NOT_CONFIGURED,
KEY_MANAGER_UNAVAILABLE,
WAIT_CREDENTIAL,
STORE_CREDENTIAL,
EXTRACT_CREDENTIAL
}
companion object {
private val TAG = AdvancedUnlockedManager::class.java.name
}
}

View File

@@ -244,7 +244,8 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.password, 1)
}
actionGoAutomatically()
val otpFieldExists = entryInfoKey?.containsCustomField(OTP_TOKEN_FIELD) ?: false
actionGoAutomatically(!otpFieldExists)
}
KEY_OTP -> {
if (entryInfoKey != null) {
@@ -280,10 +281,11 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
}
private fun actionGoAutomatically() {
private fun actionGoAutomatically(switchToPreviousKeyboardIfAllowed: Boolean = true) {
if (PreferencesUtil.isAutoGoActionEnable(this)) {
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
if (PreferencesUtil.isKeyboardPreviousFillInEnable(this)) {
if (switchToPreviousKeyboardIfAllowed
&& PreferencesUtil.isKeyboardPreviousFillInEnable(this)) {
switchToPreviousKeyboard()
}
}

View File

@@ -91,6 +91,10 @@ class EntryInfo : Parcelable {
return customFields.any { !it.protectedValue.isProtected }
}
fun containsCustomField(label: String): Boolean {
return customFields.lastOrNull { it.name == label } != null
}
fun isAutoGeneratedField(field: Field): Boolean {
return field.name == OTP_TOKEN_FIELD
}

View File

@@ -62,12 +62,12 @@ class AdvancedUnlockNotificationService : NotificationService() {
action = ACTION_REMOVE_KEYS
}
val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val deviceCredential = PreferencesUtil.isDeviceCredentialUnlockEnable(this)
val biometricUnlockEnabled = PreferencesUtil.isBiometricUnlockEnable(this)
val notificationBuilder = buildNewNotification().apply {
setSmallIcon(if (deviceCredential) {
R.drawable.notification_ic_device_unlock_24dp
} else {
setSmallIcon(if (biometricUnlockEnabled) {
R.drawable.notification_ic_fingerprint_unlock_24dp
} else {
R.drawable.notification_ic_device_unlock_24dp
})
intent?.let {
setContentTitle(getString(R.string.advanced_unlock))

View File

@@ -41,7 +41,7 @@ import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
@@ -218,7 +218,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
val tempAdvancedUnlockPreference: SwitchPreference? = findPreference(getString(R.string.temp_advanced_unlock_enable_key))
val biometricUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
BiometricUnlockDatabaseHelper.biometricUnlockSupported(activity)
AdvancedUnlockManager.biometricUnlockSupported(activity)
} else false
biometricUnlockEnablePreference?.apply {
// False if under Marshmallow
@@ -258,15 +258,18 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
}
val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
BiometricUnlockDatabaseHelper.deviceCredentialUnlockSupported(activity)
val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AdvancedUnlockManager.deviceCredentialUnlockSupported(activity)
} else false
deviceCredentialUnlockEnablePreference?.apply {
// Biometric unlock already checked
if (biometricUnlockEnablePreference?.isChecked == true)
isChecked = false
if (!deviceCredentialUnlockSupported) {
isChecked = false
setOnPreferenceClickListener { preference ->
(preference as SwitchPreference).isChecked = false
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.R)
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
.show(parentFragmentManager, "unavailableFeatureDialog")
false
}
@@ -337,9 +340,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
validate?.invoke()
deleteKeysAlertDialog?.setOnDismissListener(null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric(
AdvancedUnlockManager.deleteEntryKeyInKeystoreForBiometric(
activity,
object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback {
object : AdvancedUnlockManager.AdvancedUnlockErrorCallback {
fun showException(e: Exception) {
Toast.makeText(context,
getString(R.string.advanced_unlock_scanning_error, e.localizedMessage),
@@ -350,7 +353,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
showException(e)
}
override fun onBiometricException(e: Exception) {
override fun onGenericException(e: Exception) {
showException(e)
}
})

View File

@@ -26,6 +26,7 @@ import android.net.Uri
import androidx.preference.PreferenceManager
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.timeout.TimeoutHelper
import java.util.*
@@ -240,14 +241,23 @@ object PreferencesUtil {
fun isBiometricUnlockEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val biometricSupported = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
AdvancedUnlockManager.biometricUnlockSupported(context)
} else {
false
}
return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key),
context.resources.getBoolean(R.bool.biometric_unlock_enable_default))
&& biometricSupported
}
fun isDeviceCredentialUnlockEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// Priority to biometric unlock
val biometricAlreadySupported = isBiometricUnlockEnable(context)
return prefs.getBoolean(context.getString(R.string.device_credential_unlock_enable_key),
context.resources.getBoolean(R.bool.device_credential_unlock_enable_default))
&& !biometricAlreadySupported
}
fun isTempAdvancedUnlockEnable(context: Context): Boolean {

View File

@@ -35,6 +35,7 @@ import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
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.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionError
@@ -81,7 +82,7 @@ open class SettingsActivity
}
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()

View File

@@ -53,23 +53,19 @@ object MenuUtil {
fun onDefaultMenuOptionsItemSelected(activity: Activity,
item: MenuItem,
readOnly: Boolean = READ_ONLY_DEFAULT,
timeoutEnable: Boolean = false): Boolean {
timeoutEnable: Boolean = false) {
when (item.itemId) {
R.id.menu_contribute -> {
onContributionItemSelected(activity)
return true
}
R.id.menu_app_settings -> {
// To avoid flickering when launch settings in a LockingActivity
SettingsActivity.launch(activity, readOnly, timeoutEnable)
return true
}
R.id.menu_about -> {
val intent = Intent(activity, AboutActivity::class.java)
activity.startActivity(intent)
return true
}
else -> return true
}
}
}

View File

@@ -68,17 +68,10 @@
android:padding="0dp"
android:contentDescription="@string/about"
android:src="@drawable/ic_launcher_foreground"/>
<FrameLayout
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_advanced_unlock_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
android:id="@+id/biometric_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/colorPrimary"
android:visibility="gone"/>
</FrameLayout>
android:layout_height="match_parent" />
</FrameLayout>
<androidx.appcompat.widget.Toolbar

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
android:id="@+id/advanced_unlock_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/colorPrimary"
android:visibility="gone"/>
</FrameLayout>

View File

@@ -504,7 +504,6 @@
<string name="keyboard_save_search_info_summary">Pokuste se uložit sdílené info, když manuálné vybíráte položku</string>
<string name="keyboard_save_search_info_title">Uložit sdílené info</string>
<string name="notification">Oznámení</string>
<string name="crypto_object_not_initialized">Krypto objekt nelze načíst.</string>
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string>
<string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string>
<string name="warning_empty_recycle_bin">Trvale odstranit všechny položky z koše\?</string>

View File

@@ -491,7 +491,6 @@
<string name="database_data_remove_unlinked_attachments_summary">Fjerner vedhæftede filer indeholdt i databasen, men ikke knyttet til en post</string>
<string name="database_data_remove_unlinked_attachments_title">Fjern ikke-sammenkædede data</string>
<string name="data">Data</string>
<string name="crypto_object_not_initialized">Kryptoobjektet kunne ikke hentes.</string>
<string name="biometric_security_update_required">Biometrisk sikkerhedsopdatering påkrævet.</string>
<string name="warning_empty_keyfile_explanation">Indholdet af nøglefilen bør aldrig ændres og bør i bedste fald indeholde tilfældigt genererede data.</string>
<string name="warning_empty_keyfile">Det anbefales ikke at tilføje en tom nøglefil.</string>

View File

@@ -508,7 +508,6 @@
<string name="keyboard_previous_lock_summary">Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank</string>
<string name="keyboard_previous_lock_title">Datenbank sperren</string>
<string name="notification">Benachrichtigung</string>
<string name="crypto_object_not_initialized">Kryptoobjekt kann nicht abgerufen werden.</string>
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
<string name="configure_biometric">Es sind keine biometrischen oder Geräteanmeldeinformationen registriert.</string>
<string name="registration_mode">Registrierungsmodus</string>
@@ -529,4 +528,29 @@
<string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string>
<string name="menu_keystore_remove_key">Löschen des Schlüssels zum erweiterten Entsperren</string>
<string name="advanced_unlock_prompt_store_credential_title">Fortschrittliche Entsperrerkennung</string>
<string name="education_advanced_unlock_summary">Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.</string>
<string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string>
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string>
<string name="temp_advanced_unlock_timeout_summary">Dauer der erweiterten Entsperrung bevor sein Inhalt gelöscht wird</string>
<string name="temp_advanced_unlock_timeout_title">Verfall der erweiterten Entsperrung</string>
<string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen</string>
<string name="temp_advanced_unlock_enable_title">Temporäre erweiterte Entsperrung</string>
<string name="device_credential_unlock_enable_summary">Erlaubt Ihnn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
<string name="advanced_unlock_tap_delete">Drücken um erweiterte Entsperrschlüssel zu löschen</string>
<string name="content">Inhalt</string>
<string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit fortgeschriitener Entsperrungs-Erkennung</string>
<string name="enter">Eingabetaste</string>
<string name="backspace">Rücktaste</string>
<string name="select_entry">Wähle Eintrag</string>
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
<string name="custom_fields">Benutzerdefinierte Felder</string>
<string name="advanced_unlock_delete_all_key_warning">Löschen aller Schlüssel in Zusammenhang mit Erkennung des erweiterterten Entsperrens\?</string>
<string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string>
<string name="device_credential">Geräteanmeldedaten</string>
<string name="credential_before_click_advanced_unlock_button">Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.</string>
<string name="advanced_unlock_prompt_not_initialized">Initialisieren des erweitertes Entsperren Dialogs fehlgeschlagen.</string>
<string name="advanced_unlock_scanning_error">Erweitertes Entsperren Fehler: %1$s</string>
<string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string>
<string name="advanced_unlock_invalid_key">Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</string>
</resources>

View File

@@ -503,7 +503,6 @@
<string name="keyboard_save_search_info_summary">Προσπαθήστε να αποθηκεύσετε κοινόχρηστες πληροφορίες όταν κάνετε μια χειροκίνητη επιλογή καταχώρησης</string>
<string name="keyboard_save_search_info_title">Αποθήκευση κοινόχρηστων πληροφοριών</string>
<string name="notification">Ειδοποίηση</string>
<string name="crypto_object_not_initialized">Δεν είναι δυνατή η ανάκτηση κρυπτογραφικού αντικειμένου.</string>
<string name="biometric_security_update_required">Απαιτείται ενημέρωση βιομετρικής ασφάλειας.</string>
<string name="configure_biometric">Κανένα πιστοποιητικό βιομετρίας ή συσκευής δεν είναι εγγεγραμμένο.</string>
<string name="warning_empty_recycle_bin">Να διαγραφούν οριστικά όλοι οι κόμβοι από τον κάδο ανακύκλωσης;</string>

View File

@@ -513,7 +513,6 @@
<string name="keyboard_save_search_info_summary">Essayer denregistrer les informations partagées lors de la sélection manuelle dune entrée</string>
<string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string>
<string name="notification">Notification</string>
<string name="crypto_object_not_initialized">Impossible de récupérer l\'objet crypto.</string>
<string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string>
<string name="warning_empty_recycle_bin">Supprimer définitivement tous les nœuds de la corbeille \?</string>
<string name="registration_mode">Mode enregistrement</string>

View File

@@ -490,7 +490,6 @@
<string name="keyboard_save_search_info_summary">Pokušaj spremiti dijeljene informacije prilikom odabira ručnog unosa</string>
<string name="keyboard_save_search_info_title">Spremi dijeljene informacije</string>
<string name="warning_empty_recycle_bin">Trajno izbrisati sve čvorove iz smeća\?</string>
<string name="crypto_object_not_initialized">Nije moguće dohvatiti kripto objekt.</string>
<string name="biometric_security_update_required">Potrebno je aktualizirati biometrijsku zaštitu.</string>
<string name="configure_biometric">Ne postoji biometrijski ključ niti podaci za prijavu uređaja.</string>
<string name="registration_mode">Modus registracije</string>

View File

@@ -464,7 +464,6 @@
<string name="database_data_remove_unlinked_attachments_summary">Eltávolítja azokat a mellékleteket, melyek az adatbázisban szerepelnek, de nem tartoznak bejegyzéshez</string>
<string name="database_data_remove_unlinked_attachments_title">Nem összekapcsolt adatok eltávolítása</string>
<string name="data">Adatok</string>
<string name="crypto_object_not_initialized">A titkosítási objektum nem kérhető le.</string>
<string name="biometric_security_update_required">Biometrikus biztonsági frissítés szükséges.</string>
<string name="configure_biometric">Nincs biometrikus vagy eszközazonosító beállítva.</string>
<string name="warning_empty_keyfile_explanation">A kulcsfájl tartalmának sosem szabad megváltoznia, és a legjobb esetben véletlenszerűen előállított adatokat kellene tartalmaznia.</string>

View File

@@ -27,11 +27,11 @@
<string name="add_group">Aggiungi gruppo</string>
<string name="encryption_algorithm">Algoritmo di cifratura</string>
<string name="app_timeout">Scadenza app</string>
<string name="app_timeout_summary">Tempo di inattività prima del blocco della base di dati</string>
<string name="app_timeout_summary">Tempo di inattività prima del blocco del database</string>
<string name="application">App</string>
<string name="menu_app_settings">Impostazioni app</string>
<string name="brackets">Parentesi</string>
<string name="file_manager_install_description">Un file manager che accetta l\'azione Intent. ACTION_CREATE_DOCUMENT e ACTION_OPEN_DOCUMENT sono necessari per creare, aprire e salvare i file del database.</string>
<string name="file_manager_install_description">Un file manager che accetta intent action ACTION_CREATE_DOCUMENT e ACTION_OPEN_DOCUMENT è necessario creare, aprire e salvare i file del database.</string>
<string name="clipboard_cleared">Appunti eliminati</string>
<string name="clipboard_error_title">Errore negli appunti</string>
<string name="clipboard_error">Alcuni dispositivi non permettono alle app di usare gli appunti.</string>
@@ -39,15 +39,15 @@
<string name="clipboard_timeout">Scadenza appunti</string>
<string name="clipboard_timeout_summary">Tempo prima di eliminare gli appunti (se supportato dal dispositivo)</string>
<string name="select_to_copy">Copia %1$s negli appunti</string>
<string name="retrieving_db_key">Creazione file chiave base di dati</string>
<string name="database">Banca dati</string>
<string name="decrypting_db">Decodifica contenuto base di dati</string>
<string name="default_checkbox">Usa come base di dati predefinita</string>
<string name="retrieving_db_key">Recupero chiave database</string>
<string name="database">Database</string>
<string name="decrypting_db">Decodifica contenuto database…</string>
<string name="default_checkbox">Usa come database predefinito</string>
<string name="digits">Numeri</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft è un programma &lt;strong&gt;open-source&lt;/strong&gt; e &lt;strong&gt;senza pubblicità&lt;/strong&gt;.
\nViene distribuito sotto le condizioni della licenza &lt;strong&gt;GPL versione 3&lt;/strong&gt; o successiva, senza alcuna garanzia.</string>
<string name="entry_notes">Note</string>
<string name="select_database_file">Apri una base di dati esistente</string>
<string name="select_database_file">Apri un database esistente</string>
<string name="entry_accessed">Ultimo accesso</string>
<string name="entry_cancel">Annulla</string>
<string name="entry_confpassword">Conferma password</string>
@@ -64,15 +64,15 @@
<string name="error_arc4">La codifica a flusso Arcfour non è supportata.</string>
<string name="error_can_not_handle_uri">KeePassDX non può gestire questo URI.</string>
<string name="error_file_not_create">Impossibile creare il file</string>
<string name="error_invalid_db">Impossibile leggere la base di dati.</string>
<string name="error_invalid_db">Impossibile leggere il database.</string>
<string name="error_invalid_path">Assicurati che il percorso sia corretto.</string>
<string name="error_no_name">Inserisci un nome.</string>
<string name="error_out_of_memory">Memoria insufficiente per caricare l\'intera base di dati.</string>
<string name="error_out_of_memory">Memoria insufficiente per caricare l\'intero database.</string>
<string name="error_pass_gen_type">Deve essere selezionato almeno un tipo di generazione password.</string>
<string name="error_pass_match">Le password non corrispondono.</string>
<string name="error_rounds_too_large">«Livello» troppo alto. Impostato a 2147483648.</string>
<string name="error_string_key">Ogni stringa deve avere un nome.</string>
<string name="error_wrong_length">Inserisci un numero naturale positivo nel campo «lunghezza».</string>
<string name="error_wrong_length">Inserisci un numero intero positivo nel campo \"Lunghezza\".</string>
<string name="field_name">Nome campo</string>
<string name="field_value">Valore campo</string>
<string name="file_not_found_content">File non trovato. Prova a riaprirlo dal tuo gestore di file.</string>
@@ -87,24 +87,24 @@
<string name="hint_pass">Password</string>
<string name="invalid_credentials">Non è possibile leggere le credenziali.</string>
<string name="invalid_algorithm">Algoritmo errato.</string>
<string name="invalid_db_sig">Formato della base di dati non riconosciuto.</string>
<string name="invalid_db_sig">Formato del database non riconosciuto.</string>
<string name="keyfile_is_empty">Il file chiave è vuoto.</string>
<string name="length">Lunghezza</string>
<string name="list_size_title">Dimensione elenco elementi</string>
<string name="list_size_summary">Dimensione del testo nell\'elenco del gruppo</string>
<string name="loading_database">Caricamento della base di dati</string>
<string name="loading_database">Caricamento del database</string>
<string name="lowercase">Minuscole</string>
<string name="hide_password_title">Nascondi le password</string>
<string name="hide_password_summary">Maschera le password (***) in modo predefinito</string>
<string name="about">Informazioni</string>
<string name="menu_change_key_settings">Modifica chiave principale</string>
<string name="settings">Impostazioni</string>
<string name="menu_database_settings">Impostazioni base di dati</string>
<string name="menu_database_settings">Impostazioni database</string>
<string name="menu_delete">Elimina</string>
<string name="menu_donate">Dona</string>
<string name="menu_edit">Modifica</string>
<string name="menu_hide_password">Nascondi password</string>
<string name="menu_lock">Blocca la base di dati</string>
<string name="menu_lock">Blocca database</string>
<string name="menu_open">Apri</string>
<string name="menu_search">Cerca</string>
<string name="menu_showpass">Mostra password</string>
@@ -115,16 +115,16 @@
<string name="no_url_handler">Installa un browser web per aprire questo URL.</string>
<string name="omit_backup_search_title">Non cercare nelle voci di backup</string>
<string name="omit_backup_search_summary">Ometti i gruppi «Backup» e «Cestino» dai risultati di ricerca</string>
<string name="progress_create">Creazione di una nuova base di dati</string>
<string name="progress_create">Creazione di un nuovo database</string>
<string name="progress_title">In corso…</string>
<string name="protection">Protezione</string>
<string name="read_only">Sola lettura</string>
<string name="read_only_warning">KeePassDX richiede l\'autorizzazione di scrittura per poter modificare la tua base di dati.</string>
<string name="read_only_warning">A seconda del tuo file manager, KeepassDX potrebbe non riuscire a scrivere sulla memoria.</string>
<string name="content_description_remove_from_list">Elimina</string>
<string name="root">Root</string>
<string name="rounds">Livello cifratura</string>
<string name="rounds_explanation">Livelli di cifratura aggiuntivi forniscono una maggiore protezione contro attacchi di tipo forza bruta, ma può rallentare il caricamento e il salvataggio.</string>
<string name="saving_database">Salvataggio della base di dati</string>
<string name="saving_database">Salvataggio del database</string>
<string name="space">Spazio</string>
<string name="search_label">Cerca</string>
<string name="sort_db">Ordine naturale</string>
@@ -132,16 +132,16 @@
<string name="search">Cerca</string>
<string name="search_results">Risultati della ricerca</string>
<string name="underline">Trattino basso</string>
<string name="unsupported_db_version">Versione della base di dati non supportata.</string>
<string name="unsupported_db_version">Versione del database non supportata.</string>
<string name="uppercase">Maiuscole</string>
<string name="warning">Attenzione</string>
<string name="warning_password_encoding">Evita password con caratteri al di fuori del formato di codifica del testo nel file di base di dati (i caratteri non riconosciuti vengono convertiti nella stessa lettera).</string>
<string name="warning_password_encoding">Evita password con caratteri al di fuori del formato di codifica del testo nel file del database (i caratteri non riconosciuti vengono convertiti nella stessa lettera).</string>
<string name="version_label">Versione %1$s</string>
<string name="encrypted_value_stored">Password criptata salvata</string>
<string name="no_credentials_stored">Questa base di dati non contiene alcuna credenziale.</string>
<string name="no_credentials_stored">Questo database non contiene alcuna credenziale.</string>
<string name="education_unlock_summary">Inserisci la password o il file chiave per sbloccare la base di dati.
\n
\nEseguire il backup del file di base di dati in un luogo sicuro dopo ogni modifica.</string>
\nEseguire il backup del file del database in un luogo sicuro dopo ogni modifica.</string>
<string-array name="timeout_options">
<item>5 secondi</item>
<item>10 secondi</item>
@@ -173,7 +173,7 @@
<string name="menu_cancel">Annulla</string>
<string name="menu_file_selection_read_only">Sola lettura</string>
<string name="menu_open_file_read_and_write">Modificabile</string>
<string name="encryption_explanation">Algoritmo di cifratura della base di dati usato per tutti i dati.</string>
<string name="encryption_explanation">Algoritmo di cifratura del database usato per tutti i dati.</string>
<string name="kdf_explanation">Per generare la chiave per l\'algoritmo di cifratura, la chiave principale viene trasformata usando una funzione di derivazione della chiave (con un sale casuale).</string>
<string name="memory_usage">Utilizzo di memoria</string>
<string name="memory_usage_explanation">Quantità di memoria (in byte) utilizzabili dalla funzione di derivazione della chiave.</string>
@@ -207,19 +207,19 @@
<string name="clipboard_warning">Se l\'eliminazione automatica degli appunti fallisce, cancellali manualmente.</string>
<string name="lock">Blocca</string>
<string name="lock_database_screen_off_title">Blocco schermo</string>
<string name="lock_database_screen_off_summary">Blocca la base di dati quando lo schermo è spento</string>
<string name="lock_database_screen_off_summary">Blocca il database quando lo schermo è spento</string>
<string name="advanced_unlock">Impronta digitale</string>
<string name="biometric_unlock_enable_title">Scansione di impronte</string>
<string name="biometric_unlock_enable_summary">Consente la scansione biometrica per aprire il database</string>
<string name="biometric_delete_all_key_title">Elimina chiavi di cifratura</string>
<string name="biometric_delete_all_key_summary">Elimina tutte le chiavi di cifratura relative al riconoscimento dell\'impronta</string>
<string name="biometric_delete_all_key_summary">Elimina tutte le chiavi di cifratura relative allo sblocco avanzato</string>
<string name="unavailable_feature_text">Impossibile avviare questa funzione.</string>
<string name="unavailable_feature_version">Il dispositivo usa Android %1$s, ma richiede %2$s o versioni successive.</string>
<string name="unavailable_feature_hardware">L\'hardware relativo non è stato trovato.</string>
<string name="file_name">Nome file</string>
<string name="path">Percorso</string>
<string name="assign_master_key">Assegna una chiave master</string>
<string name="create_keepass_file">Crea una nuova base di dati</string>
<string name="create_keepass_file">Crea un nuovo database</string>
<string name="recycle_bin_title">Uso del Cestino</string>
<string name="recycle_bin_summary">Sposta i gruppi e le voci nel gruppo «Cestino» prima di eliminarlo</string>
<string name="monospace_font_fields_enable_title">Carattere campi</string>
@@ -227,11 +227,11 @@
<string name="allow_copy_password_title">Fiducia appunti</string>
<string name="allow_copy_password_summary">Consenti la copia della password e dei campi protetti negli appunti</string>
<string name="allow_copy_password_warning">Attenzione: gli appunti sono condivisi da tutte le app. Se vengono copiati dati sensibili, altri software possono recuperarli.</string>
<string name="database_name_title">Nome della base di dati</string>
<string name="database_description_title">Descrizione della base di dati</string>
<string name="database_version_title">Versione della base di dati</string>
<string name="database_name_title">Nome del database</string>
<string name="database_description_title">Descrizione del database</string>
<string name="database_version_title">Versione del database</string>
<string name="text_appearance">Testo</string>
<string name="application_appearance">App</string>
<string name="application_appearance">Interfaccia</string>
<string name="other">Altro</string>
<string name="keyboard">Tastiera</string>
<string name="magic_keyboard_title">Magitastiera</string>
@@ -239,20 +239,20 @@
<string name="allow_no_password_title">Non consentire nessuna chiave principale</string>
<string name="allow_no_password_summary">Permetti di toccare il pulsante \"Apri\" se non sono selezionate credenziali</string>
<string name="enable_read_only_title">Protetto da scrittura</string>
<string name="enable_read_only_summary">Apri la base di dati in sola lettura in modo predefinito</string>
<string name="enable_read_only_summary">Apri il database in sola lettura in modo predefinito</string>
<string name="enable_education_screens_title">Suggerimenti educativi</string>
<string name="enable_education_screens_summary">Evidenzia gli elementi per imparare come funziona l\'app</string>
<string name="reset_education_screens_title">Ripristina i suggerimenti educativi</string>
<string name="reset_education_screens_summary">Mostra di nuovo tutte le informazioni educative</string>
<string name="reset_education_screens_text">Suggerimenti educativi ripristinati</string>
<string name="education_create_database_title">Crea il tuo file di base di dati</string>
<string name="education_create_database_title">Crea il tuo file database</string>
<string name="education_create_database_summary">Crea il tuo primo file di gestione password.</string>
<string name="education_select_database_title">Apri una base di dati esistente</string>
<string name="education_select_database_summary">Apri il tuo file di base di dati precedente dal tuo gestore di file per continuare ad usarlo.</string>
<string name="education_new_node_title">Aggiungi elementi alla tua base di dati</string>
<string name="education_select_database_title">Apri un database esitente</string>
<string name="education_select_database_summary">Apri il tuo file database usato in precedenza con il file manager per continuare ad usarlo.</string>
<string name="education_new_node_title">Aggiungi elementi al tuo database</string>
<string name="education_new_node_summary">Gli elementi aiutano a gestire le tue identità digitali.
\n
\nI gruppi (come cartelle) organizzano gli elementi nella base di dati.</string>
\nI gruppi (come cartelle) organizzano gli elementi nel database.</string>
<string name="education_search_title">Cerca tra gli elementi</string>
<string name="education_search_summary">Inserisci il titolo, il nome utente o il contenuto di altri campi per recuperare le tue password.</string>
<string name="education_entry_edit_title">Modifica l\'elemento</string>
@@ -261,18 +261,18 @@
<string name="education_generate_password_summary">Genera una password robusta da associare all\'elemento, definiscila a seconda dei criteri del modulo e non dimenticare di tenerla al sicuro.</string>
<string name="education_entry_new_field_title">Aggiungi campi personalizzati</string>
<string name="education_entry_new_field_summary">Registra un campo aggiuntivo, aggiungi un valore e facoltativamente proteggilo.</string>
<string name="education_unlock_title">Sblocca la tua base di dati</string>
<string name="education_read_only_title">Proteggi da scrittura la tua base di dati</string>
<string name="education_unlock_title">Sblocca il tuo database</string>
<string name="education_read_only_title">Proteggi da scrittura il tuo database</string>
<string name="education_read_only_summary">Cambia la modalità di apertura per la sessione.
\n
\n«Sola lettura» impedisce modifiche accidentali alla base di dati.
\n«Sola lettura» impedisce modifiche accidentali al databae.
\n«Modificabile» permette di aggiungere, eliminare o modificare tutti gli elementi.</string>
<string name="education_field_copy_title">Copia un campo</string>
<string name="education_field_copy_summary">I campi copiati possono essere incollati ovunque.
\n
\nUsa il metodo di inserimento che preferisci.</string>
<string name="education_lock_title">Blocca la base di dati</string>
<string name="education_lock_summary">Blocca velocemente la base di dati, puoi impostare l\'applicazione per bloccarla dopo un certo periodo e quando lo schermo si spegne.</string>
<string name="education_lock_title">Blocca il database</string>
<string name="education_lock_summary">Blocca velocemente il database, puoi impostare l\'applicazione per bloccarsi dopo un certo periodo e quando lo schermo si spegne.</string>
<string name="education_sort_title">Ordine elementi</string>
<string name="education_sort_summary">Scegli l\'ordine di elementi e gruppi.</string>
<string name="education_donation_title">Partecipa</string>
@@ -288,7 +288,7 @@
<string name="html_text_dev_feature_encourage">stai incoraggiando gli sviluppatori a creare &lt;strong&gt;nuove funzionalità&lt;/strong&gt; e a &lt;strong&gt;correggere errori&lt;/strong&gt; in base alle tue osservazioni.</string>
<string name="html_text_dev_feature_thanks">Grazie mille per il tuo contributo.</string>
<string name="html_text_dev_feature_work_hard">Stiamo lavorando sodo per rilasciare questa funzione a breve.</string>
<string name="html_text_dev_feature_upgrade">Non dimenticare di tenere aggiornata l\'app installando nuove versioni.</string>
<string name="html_text_dev_feature_upgrade">Ricorda di tenere aggiornata l\'app installando le nuove versioni.</string>
<string name="download">Scarica</string>
<string name="contribute">Contribuisci</string>
<string name="encryption_rijndael">Rijndael (AES)</string>
@@ -300,7 +300,7 @@
<string name="icon_pack_choose_title">Pacchetto icone</string>
<string name="icon_pack_choose_summary">Pacchetto icone usato nell\'app</string>
<string name="edit_entry">Modifica elemento</string>
<string name="error_load_database">Caricamento della base di dati fallito.</string>
<string name="error_load_database">Caricamento del database fallito.</string>
<string name="error_load_database_KDF_memory">Caricamento della chiave fallito. Prova a diminuire l\'«Utilizzo memoria» del KDF.</string>
<string name="list_entries_show_username_title">Mostra nomi utente</string>
<string name="list_entries_show_username_summary">Mostra i nomi utente negli elenchi</string>
@@ -318,7 +318,7 @@
<string name="keyboard_notification_entry_content_title">%1$s disponibile nella Magitastiera</string>
<string name="keyboard_notification_entry_content_text">%1$s</string>
<string name="keyboard_notification_entry_clear_close_title">Pulisci alla chiusura</string>
<string name="keyboard_notification_entry_clear_close_summary">Chiudere la base di dati alla chiusura della notifica</string>
<string name="keyboard_notification_entry_clear_close_summary">Chiudere il database alla chiusura della notifica</string>
<string name="keyboard_appearance_category">Aspetto</string>
<string name="keyboard_theme_title">Tema tastiera</string>
<string name="keyboard_keys_category">Tasti</string>
@@ -327,14 +327,14 @@
<string name="selection_mode">Modalità selezione</string>
<string name="do_not_kill_app">Non terminare l\'app…</string>
<string name="lock_database_back_root_title">Premere \'\'Indietro\'\' per bloccare</string>
<string name="lock_database_back_root_summary">Blocca la base di dati quando l\'utente preme il pulsante Indietro nella schermata principale</string>
<string name="lock_database_back_root_summary">Blocca il database quando l\'utente preme il pulsante Indietro nella schermata principale</string>
<string name="clear_clipboard_notification_title">Pulisci alla chiusura</string>
<string name="clear_clipboard_notification_summary">Blocca la base di dati quando scade la durata degli appunti o la notifica viene chiusa dopo che inizi ad usarlo</string>
<string name="clear_clipboard_notification_summary">Blocca il database quando scade la durata degli appunti o la notifica viene chiusa dopo che inizi ad usarlo</string>
<string name="recycle_bin">Cestino</string>
<string name="keyboard_selection_entry_title">Selezione elemento</string>
<string name="keyboard_selection_entry_summary">Mostra i campi di input nella Magitastiera durante la visualizzazione di un elemento</string>
<string name="delete_entered_password_title">Elimina password</string>
<string name="delete_entered_password_summary">Elimina la password immessa dopo un tentativo di connessione alla base di dati</string>
<string name="delete_entered_password_summary">Elimina la password immessa dopo un tentativo di connessione al database</string>
<string name="content_description_open_file">Apri il file</string>
<string name="content_description_node_children">Figli del nodo</string>
<string name="content_description_add_node">Aggiungi un nodo</string>
@@ -358,7 +358,7 @@
<string name="security">Sicurezza</string>
<string name="content_description_background">Sfondo</string>
<string name="entry_UUID">Identificativo univoco universale</string>
<string name="error_create_database_file">Impossibile creare una base di dati con questa password e file chiave.</string>
<string name="error_create_database_file">Impossibile creare un database con questa password e file chiave.</string>
<string name="menu_advanced_unlock_settings">Sblocco avanzato</string>
<string name="entry_history">Cronologia</string>
<string name="entry_setup_otp">Imposta password usa e getta</string>
@@ -377,16 +377,16 @@
<string name="error_otp_period">Il periodo deve essere tra %1$d e %2$d secondi.</string>
<string name="error_otp_digits">Il token deve contenere tra %1$d e %2$d cifre.</string>
<string name="invalid_db_same_uuid">%1$s con le stesse credenziali univoche %2$s è già esistente.</string>
<string name="creating_database">Sto creando la base di dati</string>
<string name="creating_database">Sto creando il database</string>
<string name="menu_security_settings">Impostazioni di sicurezza</string>
<string name="contains_duplicate_uuid">Il database contiene Identificativi Univoci Universali (UUID) duplicati.</string>
<string name="error_save_database">Non è possibile salvare la base di dati.</string>
<string name="menu_save_database">Salva la base di dati</string>
<string name="error_save_database">Non è possibile salvare il database.</string>
<string name="menu_save_database">Salva database</string>
<string name="menu_empty_recycle_bin">Svuota il cestino</string>
<string name="command_execution">Esecuzione del comando…</string>
<string name="warning_permanently_delete_nodes">Vuoi eliminare definitivamente i nodi selezionati\?</string>
<string name="entry_attachments">Allegati</string>
<string name="auto_focus_search_summary">Richiedi una ricerca quando una base di dati viene aperta</string>
<string name="auto_focus_search_summary">Richiedi una ricerca quando un database viene aperto</string>
<string name="auto_focus_search_title">Ricerca rapida</string>
<string name="menu_delete_entry_history">Cancella cronologia</string>
<string name="menu_restore_entry_history">Ripristina cronologia</string>
@@ -396,32 +396,32 @@
<string name="menu_master_key_settings">Impostazioni della chiave principale</string>
<string name="master_key">Chiave principale</string>
<string name="contribution">Contributi</string>
<string name="warning_database_read_only">Garantisci il permesso di scrittura per salvare i cambiamenti della base di dati</string>
<string name="hide_broken_locations_summary">Nascondi collegamenti corrotti nella lista delle basi di dati recenti</string>
<string name="hide_broken_locations_title">Nascondi i collegamenti di basi di dati corrotti</string>
<string name="show_recent_files_summary">Mostra le posizioni delle basi di dati recenti</string>
<string name="warning_database_read_only">Concedi il permesso di scrittura per salvare i cambiamenti del database</string>
<string name="hide_broken_locations_summary">Nascondi collegamenti corrotti nella lista dei database recenti</string>
<string name="hide_broken_locations_title">Nascondi i collegamenti dei database corrotti</string>
<string name="show_recent_files_summary">Mostra le posizioni dei database recenti</string>
<string name="show_recent_files_title">Mostra file recenti</string>
<string name="remember_keyfile_locations_title">Ricorda la posizione dei file chiave</string>
<string name="remember_database_locations_summary">Ricorda la posizione delle basi di dati</string>
<string name="remember_database_locations_title">Ricorda la posizione delle basi di dati</string>
<string name="remember_database_locations_summary">Ricorda la posizione dei database</string>
<string name="remember_database_locations_title">Ricorda la posizione dei database</string>
<string name="contains_duplicate_uuid_procedure">Per continuare, risolvi il problema generando nuovi UUID per i duplicati\?</string>
<string name="error_create_database">Impossibile creare il file della base di dati.</string>
<string name="error_create_database">Impossibile creare il file del database.</string>
<string name="entry_add_attachment">Aggiungi allegato</string>
<string name="discard">Scarta</string>
<string name="discard_changes">Scartare i cambiamenti\?</string>
<string name="validate">Convalida</string>
<string name="max_history_size_title">Dimensione massima</string>
<string name="max_history_items_title">Numero massimo</string>
<string name="biometric_auto_open_prompt_title">Apri automaticamente prompt biometrico</string>
<string name="biometric_auto_open_prompt_title">Apri automaticamente la richiesta</string>
<string name="max_history_size_summary">Limita la dimensione (in byte) della cronologia per voce</string>
<string name="max_history_items_summary">Limita il numero di elementi della cronologia per voce</string>
<string name="recycle_bin_group_title">Gruppo cestino</string>
<string name="database_data_compression_summary">La compressione dei dati riduce le dimensioni della base di dati</string>
<string name="database_data_compression_summary">La compressione dei dati riduce le dimensioni del database</string>
<string name="database_data_compression_title">Compressione dati</string>
<string name="biometric_auto_open_prompt_summary">Proponi l\'autenticazione biometrica quando la base di dati è configurata per usarla</string>
<string name="advanced_unlock_explanation_summary">Utilizza lo sblocco avanzato per aprire la base di dati più facilmente</string>
<string name="biometric_auto_open_prompt_summary">Richiedi automaticamente lo sblocco avanzato se il database è impostato per usarlo</string>
<string name="advanced_unlock_explanation_summary">Utilizza lo sblocco avanzato per aprire il database più facilmente</string>
<string name="clipboard_explanation_summary">Copia i campi di immissione utilizzando gli appunti del tuo dispositivo</string>
<string name="database_opened">Base di dati aperta</string>
<string name="database_opened">Database aperto</string>
<string name="biometric">Biometrico</string>
<string name="settings_database_force_changing_master_key_title">Forza rinnovo</string>
<string name="settings_database_recommend_changing_master_key_summary">È consigliato di cambiare la chiave principale (giorni)</string>
@@ -435,7 +435,7 @@
<string name="download_initialization">Inizializzazione…</string>
<string name="download_attachment">Scaricamento %1$s</string>
<string name="education_setup_OTP_title">Imposta One-Time Password (OTP)</string>
<string name="enable_auto_save_database_summary">Salva la base di dati dopo ogni azione importante (in modalità «Modificabile»)</string>
<string name="enable_auto_save_database_summary">Salva il database dopo ogni azione importante (in modalità «Modificabile»)</string>
<string name="enable_auto_save_database_title">Salvataggio automatico del database</string>
<string name="autofill_auto_search_summary">Suggerisci automaticamente risultati dal dominio web o ID dell\'applicazione</string>
<string name="autofill_auto_search_title">Ricerca automatica</string>
@@ -445,7 +445,7 @@
<string name="compression_gzip">Gzip</string>
<string name="compression_none">Nessuna</string>
<string name="compression">Compressione</string>
<string name="database_custom_color_title">Colore personalizzato della base di dati</string>
<string name="database_custom_color_title">Colore personalizzato del database</string>
<string name="database_default_username_title">Nome utente predefinito</string>
<string name="disable">Disabilita</string>
<string name="enable">Abilita</string>
@@ -456,7 +456,7 @@
<string name="lock_database_show_button_title">Mostra il bottone di blocco</string>
<string name="autofill_preference_title">Impostazioni dell\'autocompletamento</string>
<string name="warning_database_link_revoked">Accesso al file revocato dal file manager</string>
<string name="remember_keyfile_locations_summary">Ricorda la posizione dei file chiave dei basi di dati</string>
<string name="remember_keyfile_locations_summary">Ricorda la posizione dei file chiave</string>
<string name="error_label_exists">Questa etichetta esiste già.</string>
<string name="autofill_block_restart">Riavvia l\'app contenente il campo per attivare il blocco.</string>
<string name="autofill_block">Blocca riempimento automatico</string>
@@ -473,24 +473,24 @@
<string name="content_description_add_item">Aggiungi elemento</string>
<string name="keyboard_previous_fill_in_summary">Torna automaticamente alla tastiera precedente quando si esegue l\'azione del tasto automatico</string>
<string name="keyboard_previous_fill_in_title">Azione tasto automatico</string>
<string name="keyboard_previous_database_credentials_summary">Torna automaticamente alla tastiera precedente nella schermata delle credenziali della base di dati</string>
<string name="keyboard_previous_database_credentials_title">Schermata credenziali della base di dati</string>
<string name="keyboard_previous_database_credentials_summary">Torna automaticamente alla tastiera precedente nella schermata credenziali del database</string>
<string name="keyboard_previous_database_credentials_title">Schermata credenziali del database</string>
<string name="keyboard_change">Cambia tastiera</string>
<string name="upload_attachment">Carica %1$s</string>
<string name="education_add_attachment_summary">Carica un allegato alla voce per salvare dati esterni importanti.</string>
<string name="education_add_attachment_title">Aggiungi allegato</string>
<string name="database_data_remove_unlinked_attachments_summary">Rimuovi gli allegati contenuti nella base di dati ma non collegati ad una voce</string>
<string name="database_data_remove_unlinked_attachments_summary">Rimuovi gli allegati contenuti nel database ma non collegati ad una voce</string>
<string name="database_data_remove_unlinked_attachments_title">Rimuovi i dati scollegati</string>
<string name="data">Dati</string>
<string name="warning_empty_keyfile_explanation">Il contenuto del file chiave non deve mai essere modificato e, nel migliore dei casi, dovrebbe contenere dati generati casualmente.</string>
<string name="warning_empty_keyfile">Non è consigliabile aggiungere un file chiave vuoto.</string>
<string name="warning_sure_remove_data">Rimuovere questi dati comunque\?</string>
<string name="warning_remove_unlinked_attachment">La rimozione di dati scollegati può ridurre le dimensioni della base di dati, ma può anche eliminare i dati utilizzati per i plugin KeePass.</string>
<string name="warning_remove_unlinked_attachment">La rimozione di dati scollegati può ridurre le dimensioni del database, ma può anche eliminare i dati utilizzati per i plugin KeePass.</string>
<string name="warning_sure_add_file">Aggiungi comunque il file\?</string>
<string name="warning_replace_file">Caricare questo file sostituirà quello esistente.</string>
<string name="warning_file_too_big">Una base di dati KeePass dovrebbe contenere solo piccoli file di utilità (come i file di chiavi PGP).
<string name="warning_file_too_big">Un database KeePass dovrebbe contenere solo piccoli file di utilità (come i file di chiavi PGP).
\n
\nLa base di dati può diventare molto grande e ridurre le prestazioni con questo caricamento.</string>
\nIl tuo database può diventare molto grande e ridurre le prestazioni con questo caricamento.</string>
<string name="content_description_credentials_information">Info credenziali</string>
<string name="show_uuid_summary">Visualizza l\'UUID collegato a una voce</string>
<string name="show_uuid_title">Mostra UUID</string>
@@ -499,20 +499,49 @@
<string name="autofill_ask_to_save_data_title">Chiedi di salvare i dati</string>
<string name="autofill_save_search_info_summary">Prova a salvare le informazioni di ricerca quando effettui una selezione di immissione manuale</string>
<string name="autofill_save_search_info_title">Salva le informazioni di ricerca</string>
<string name="autofill_close_database_summary">Chiudi la base di dati dopo una selezione di compilazione automatica</string>
<string name="autofill_close_database_title">Chiudi la base di dati</string>
<string name="keyboard_previous_lock_summary">Torna automaticamente alla tastiera precedente dopo aver bloccato la base di dati</string>
<string name="keyboard_previous_lock_title">Blocca la base di dati</string>
<string name="autofill_close_database_summary">Chiudi il database dopo aver usato l\'autocompletamento</string>
<string name="autofill_close_database_title">Chiudi database</string>
<string name="keyboard_previous_lock_summary">Torna automaticamente alla tastiera precedente dopo aver bloccato il database</string>
<string name="keyboard_previous_lock_title">Blocca il database</string>
<string name="keyboard_save_search_info_summary">Prova a salvare le informazioni condivise quando effettui una selezione di immissione manuale</string>
<string name="keyboard_save_search_info_title">Salva le informazioni condivise</string>
<string name="notification">Notifica</string>
<string name="crypto_object_not_initialized">Impossibile recuperare l\'oggetto crittografico.</string>
<string name="biometric_security_update_required">È necessario un aggiornamento della sicurezza biometrica.</string>
<string name="warning_empty_recycle_bin">Eliminare definitivamente tutti i nodi dal cestino\?</string>
<string name="registration_mode">Modalità registrazione</string>
<string name="save_mode">Modalità salvataggio</string>
<string name="search_mode">Modalità ricerca</string>
<string name="error_field_name_already_exists">Il nome del campo esiste già.</string>
<string name="error_registration_read_only">Il salvataggio di un nuovo elemento non è consentito in una base di dati di sola lettura</string>
<string name="error_registration_read_only">Il salvataggio di un nuovo elemento non è consentito in un database di sola lettura</string>
<string name="configure_biometric">Nessuna credenziale biometrica o del dispositivo è registrata.</string>
<string name="education_advanced_unlock_summary">Collega la password alla tua autenticazione biometrica (o del dispositivo) per sbloccare velocemente il database.</string>
<string name="education_advanced_unlock_title">Sblocco avanzato del database</string>
<string name="enter">Invio</string>
<string name="backspace">Backspace</string>
<string name="select_entry">Seleziona voce</string>
<string name="back_to_previous_keyboard">Torna alla tasitera precedente</string>
<string name="custom_fields">Campi personalizzati</string>
<string name="advanced_unlock_delete_all_key_warning">Vuoi eliminare le chiavi di cifratura relative allo sblocco avanzato\?</string>
<string name="advanced_unlock_timeout">Scadenza sblocco avanzato</string>
<string name="temp_advanced_unlock_enable_summary">Non salvare alcun contenuto criptato per usare lo sblocco avanzato</string>
<string name="temp_advanced_unlock_timeout_summary">Validità dello sblocco avanzato prima di eliminarne il contenuto</string>
<string name="temp_advanced_unlock_timeout_title">Scadenza sblocco avanzato</string>
<string name="temp_advanced_unlock_enable_title">Sblocco avanzato temporaneo</string>
<string name="device_credential_unlock_enable_summary">Utilizza le credenziali del dispositivo per sbloccare il database</string>
<string name="device_credential_unlock_enable_title">Sblocco con credenziali dispositivo</string>
<string name="advanced_unlock_tap_delete">Tocca per eliminare le chiavi di sblocco avanzato</string>
<string name="content">Contenuto</string>
<string name="advanced_unlock_prompt_not_initialized">Non è possibile inizializzare lo sblocco avanzato.</string>
<string name="advanced_unlock_not_recognized">Non è possibile riconoscere lo sblocco avanzato</string>
<string name="advanced_unlock_invalid_key">Non è possibile leggere la chiave di sblocco avanzato. Eliminala e ripeti la prodecura dello sblocco.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Estrai le credenziali del database con i dati dallo sblocco avanzato</string>
<string name="advanced_unlock_prompt_store_credential_message">Attenzione: dovrai sempre ricordare la password principale anche se usi lo sblocco avanzato.</string>
<string name="advanced_unlock_prompt_store_credential_title">Riconoscimento con sblocco avanzato</string>
<string name="device_credential">Credenziali del dispositivo</string>
<string name="credential_before_click_advanced_unlock_button">Inserisci la password, quindi clicca sull\'icona \"Sblocco avanzato\".</string>
<string name="advanced_unlock_scanning_error">Errore sblocco avanzato: %1$s</string>
<string name="advanced_unlock_prompt_extract_credential_title">Apri il database autenticando con lo sblocco avanzato</string>
<string name="open_advanced_unlock_prompt_unlock_database">Autentica con lo sblocco avanzato per sbloccare il database</string>
<string name="open_advanced_unlock_prompt_store_credential">Autentica con lo sblocco avanzato per salvare le credenziali</string>
<string name="menu_keystore_remove_key">Elimina chiave di sblocco avanzato</string>
</resources>

View File

@@ -273,7 +273,6 @@
<string name="keystore_not_accessible">キーストアが正しく初期化されていません。</string>
<string name="encrypted_value_stored">保存された暗号化済みパスワード</string>
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
<string name="crypto_object_not_initialized">crypto オブジェクトを取得できません。</string>
<string name="database_history">履歴</string>
<string name="menu_appearance_settings">デザイン</string>
<string name="biometric">生体認証</string>

View File

@@ -417,7 +417,7 @@
<string name="hide_broken_locations_summary">Skjul ødelagte lenker i listen over nylige databaser</string>
<string name="hide_broken_locations_title">Skjul ødelagte databaselenker</string>
<string name="autofill_ask_to_save_data_title">Spør om lagring av data</string>
<string name="advanced_unlock_scanning_error">Avansert opplåsningsfeil:</string>
<string name="advanced_unlock_scanning_error">Avansert opplåsningsfeil: %1$s</string>
<string name="warning_empty_keyfile">Det anbefales ikke å legge til en tom nøkkelfil.</string>
<string name="warning_sure_add_file">Legg til filen uansett\?</string>
<string name="registration_mode">Registreringsmodus</string>

View File

@@ -34,7 +34,7 @@
<string name="file_manager_install_description">Bestandsbeheer dat de Intent-actie ACTION_CREATE_DOCUMENT en ACTION_OPEN_DOCUMENT accepteert, is nodig om databasebestanden aan te maken, te openen en op te slaan.</string>
<string name="clipboard_cleared">Klembord gewist</string>
<string name="clipboard_timeout">Klembordtime-out</string>
<string name="clipboard_timeout_summary">Tijd van opslag op het klembord (indien ondersteund op jouw apparaat)</string>
<string name="clipboard_timeout_summary">Tijd van opslag op het klembord (voor zover ondersteund op dit apparaat)</string>
<string name="select_to_copy">Selecteer om %1$s naar klembord te kopiëren</string>
<string name="retrieving_db_key">Databasesleutel ophalen…</string>
<string name="database">Database</string>
@@ -67,7 +67,7 @@
<string name="error_out_of_memory">Onvoldoende vrij geheugen om de gehele database te laden.</string>
<string name="error_pass_gen_type">Je moet minimaal één soort wachtwoordgenerering kiezen.</string>
<string name="error_pass_match">De wachtwoorden komen niet overeen.</string>
<string name="error_rounds_too_large">\"Cycli-waarde\" te groot. Wordt ingesteld op 2147483648.</string>
<string name="error_rounds_too_large">\"Cycli-waarde\" te groot. Deze wordt ingesteld op 2147483648.</string>
<string name="error_wrong_length">Voer een positief geheel getal in in het veld \"Lengte\".</string>
<string name="file_browser">Bestandsbeheer</string>
<string name="generate_password">Wachtwoord genereren</string>
@@ -81,16 +81,16 @@
<string name="invalid_credentials">Kan referenties niet lezen.</string>
<string name="invalid_db_sig">Databaseformaat kan niet worden herkend.</string>
<string name="length">Lengte</string>
<string name="list_size_title">Lengte van lijst met items</string>
<string name="list_size_summary">Tekstgrootte in de lijst</string>
<string name="list_size_title">Lijstgrootte</string>
<string name="list_size_summary">Tekstgrootte in de itemslijst</string>
<string name="loading_database">Database laden…</string>
<string name="lowercase">Kleine letters</string>
<string name="hide_password_title">Wachtwoorden verbergen</string>
<string name="hide_password_summary">Wachtwoorden standaard verbergen (***)</string>
<string name="hide_password_summary">Wachtwoorden standaard maskeren (***)</string>
<string name="about">Over</string>
<string name="menu_change_key_settings">Hoofdsleutel wijzigen</string>
<string name="settings">Instellingen</string>
<string name="menu_database_settings">Instellingen database</string>
<string name="menu_database_settings">Database-instellingen</string>
<string name="menu_delete">Verwijderen</string>
<string name="menu_donate">Doneren</string>
<string name="menu_edit">Bewerken</string>
@@ -205,15 +205,15 @@
<string name="autofill">Auto-aanvullen</string>
<string name="autofill_service_name">KeePassDX auto-aanvullendienst</string>
<string name="autofill_sign_in_prompt">Inloggen met KeePassDX</string>
<string name="set_autofill_service_title">Dienst voor automatisch aanvullen</string>
<string name="set_autofill_service_title">Dienst automatisch aanvullen</string>
<string name="autofill_explanation_summary">Schakel de dienst in om formulieren in andere apps in te vullen</string>
<string name="password_size_title">Gegenereerde wachtwoordlengte</string>
<string name="password_size_summary">Standaardlengte van gegenereerd wachtwoord instellen</string>
<string name="password_size_summary">Stel de standaardlengte van gegenereerd wachtwoord in</string>
<string name="list_password_generator_options_title">Wachtwoordtekens</string>
<string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string>
<string name="clipboard">Klembord</string>
<string name="clipboard_notifications_title">Klembordmeldingen</string>
<string name="clipboard_notifications_summary">Toon klembordmeldingen om velden te kopiëren bij het bekijken van een item</string>
<string name="clipboard_notifications_summary">Schakel klembordmeldingen in om velden te kopiëren bij het bekijken van een item</string>
<string name="clipboard_warning">Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.</string>
<string name="lock">Vergrendelen</string>
<string name="lock_database_screen_off_title">Schermvergrendeling</string>
@@ -222,9 +222,9 @@
<string name="biometric_unlock_enable_title">Ontgrendelen met biometrie</string>
<string name="biometric_unlock_enable_summary">Gebruik biometrische herkenning om de database te openen</string>
<string name="biometric_delete_all_key_title">Coderingssleutels verwijderen</string>
<string name="biometric_delete_all_key_summary">Alle sleutels voor biometrische herkenning verwijderen</string>
<string name="biometric_delete_all_key_summary">Alle coderingssleutels met betrekking tot geavanceerde ontgrendelingsherkenning verwijderen</string>
<string name="unavailable_feature_text">Kan deze functie niet starten.</string>
<string name="unavailable_feature_version">Het apparaat draait op Android %1$s, maar %2$s of hoger is vereist.</string>
<string name="unavailable_feature_version">Dit apparaat draait op Android %1$s, maar %2$s of hoger is vereist.</string>
<string name="unavailable_feature_hardware">De bijbehorende hardware werd niet gevonden.</string>
<string name="file_name">Bestandsnaam</string>
<string name="path">Pad</string>
@@ -241,19 +241,19 @@
<string name="database_description_title">Databaseomschrijving</string>
<string name="database_version_title">Databaseversie</string>
<string name="text_appearance">Tekst</string>
<string name="application_appearance">App</string>
<string name="application_appearance">Gebruikersomgeving</string>
<string name="other">Overig</string>
<string name="keyboard">Toetsenbord</string>
<string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren</string>
<string name="magic_keyboard_explanation_summary">Activeer een aangepast toetsenbord dat je wachtwoorden en identiteitsvelden vult</string>
<string name="allow_no_password_title">Geen hoofdwachtwoord toestaan</string>
<string name="allow_no_password_summary">Maakt het mogelijk op de knop \"Openen\" te tikken als er geen inloggegevens zijn geselecteerd</string>
<string name="allow_no_password_summary">Schakel de knop \"Openen\" in als er geen referenties zijn geselecteerd</string>
<string name="enable_read_only_title">Alleen-lezen</string>
<string name="enable_read_only_summary">Open de database standaard alleen-lezen</string>
<string name="enable_education_screens_title">Informatieve tips</string>
<string name="enable_education_screens_summary">Markeer elementen om te leren hoe de app werkt</string>
<string name="reset_education_screens_title">Informatieve tips opnieuw instellen</string>
<string name="reset_education_screens_summary">Informatieve tips opnieuw weergeven</string>
<string name="reset_education_screens_summary">Informatieve tips opnieuw tonen</string>
<string name="reset_education_screens_text">Informatieve tips opnieuw ingesteld</string>
<string name="education_create_database_title">Maak je databasebestand aan</string>
<string name="education_create_database_summary">Maak je eerste wachtwoordbeheerbestand aan.</string>
@@ -273,10 +273,10 @@
<string name="education_entry_new_field_summary">Registreer een extra veld, voeg een waarde toe en bescherm dit desgewenst.</string>
<string name="education_unlock_title">Ontgrendel je database</string>
<string name="education_read_only_title">Database alleen-lezen</string>
<string name="education_read_only_summary">Wijzig de opstartmodus van de sessie.
\n
\nIn alleen-lezenmodus kunnen geen onbedoelde wijzigingen worden gemaakt.
\nIn schrijfmodus kun je elementen toevoegen, verwijderen of aanpassen.</string>
<string name="education_read_only_summary">Wijzig de opstartmodus van de sessie.
\n
\nIn alleen-lezenmodus kunnen geen onbedoelde wijzigingen worden gemaakt.
\nIn schrijfmodus kun je naar wens elementen toevoegen, verwijderen of aanpassen.</string>
<string name="education_field_copy_title">Veld kopiëren toestaan</string>
<string name="education_field_copy_summary">Kopieer een veld en plak de inhoud waar je maar wilt.
\n
@@ -297,15 +297,15 @@
<string name="html_text_dev_feature_encourage">motiveer je ontwikkelaars om &lt;strong&gt;nieuwe functies&lt;/strong&gt; te creëren en &lt;strong&gt;problemen op te lossen&lt;/strong&gt;.</string>
<string name="html_text_dev_feature_thanks">Hartelijk bedankt voor je bijdrage.</string>
<string name="html_text_dev_feature_work_hard">We zijn druk bezig om deze functie snel beschikbaar te stellen.</string>
<string name="html_text_dev_feature_upgrade">Vergeet niet om je app up-to-date te houden door nieuwe versies te installeren.</string>
<string name="html_text_dev_feature_upgrade">Vergeet niet je app up-to-date te houden door nieuwe versies te installeren.</string>
<string name="download">Downloaden</string>
<string name="contribute">Bijdragen</string>
<string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string>
<string name="style_choose_title">App-thema</string>
<string name="style_choose_summary">Thema gebruikt in de app</string>
<string name="icon_pack_choose_title">Pictogrammenverzameling</string>
<string name="icon_pack_choose_summary">Pictogrammenverzameling in gebruik</string>
<string name="icon_pack_choose_title">Icon pack</string>
<string name="icon_pack_choose_summary">Gebruikt Icon Pack</string>
<string name="build_label">Build %1$s</string>
<string name="keyboard_name">Magikeyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
@@ -328,14 +328,14 @@
<string name="selection_mode">Selectiemodus</string>
<string name="do_not_kill_app">App niet afsluiten…</string>
<string name="lock_database_back_root_title">Druk \'Terug\' om te vergrendelen</string>
<string name="lock_database_back_root_summary">Vergrendel de database wanneer de gebruiker op de knop Terug in het hoofdscherm klikt</string>
<string name="lock_database_back_root_summary">Vergrendel de database wanneer de gebruiker in het hoofdscherm op de knop Terug klikt</string>
<string name="clear_clipboard_notification_title">Wissen bij afsluiten</string>
<string name="clear_clipboard_notification_summary">Vergrendel de database wanneer de duur van het klembord verloopt of de melding wordt gesloten nadat u deze bent gaan gebruiken</string>
<string name="recycle_bin">Prullenmand</string>
<string name="keyboard_selection_entry_title">Itemselectie</string>
<string name="keyboard_selection_entry_summary">Invoervelden in Magikeyboard tonen bij het bekijken van een item</string>
<string name="delete_entered_password_title">Wachtwoord wissen</string>
<string name="delete_entered_password_summary">Wis het ingevoerde wachtwoord na een poging met een database te verbinden</string>
<string name="delete_entered_password_summary">Wis het ingevoerde wachtwoord na een verbindingspoging met een database</string>
<string name="content_description_open_file">Bestand openen</string>
<string name="content_description_node_children">Onderliggende items</string>
<string name="content_description_add_node">Knooppunt toevoegen</string>
@@ -353,7 +353,7 @@
<string name="entry_UUID">UUID</string>
<string name="error_move_entry_here">Je kan hier geen item plaatsen.</string>
<string name="error_copy_entry_here">Je kan hier geen item kopiëren.</string>
<string name="list_groups_show_number_entries_title">Toon het aantal items</string>
<string name="list_groups_show_number_entries_title">Aantal items tonen</string>
<string name="list_groups_show_number_entries_summary">Toon het aantal items in een groep</string>
<string name="content_description_background">Achtergrond</string>
<string name="content_description_update_from_list">Update</string>
@@ -361,8 +361,8 @@
<string name="error_create_database_file">Kan geen database aanmaken met dit wachtwoord en sleutelbestand.</string>
<string name="menu_advanced_unlock_settings">Geavanceerd ontgrendelen</string>
<string name="biometric">Biometrie</string>
<string name="biometric_auto_open_prompt_title">Automatisch om biometrie vragen</string>
<string name="biometric_auto_open_prompt_summary">Automatisch om biometrie vragen als een database hiervoor is ingesteld</string>
<string name="biometric_auto_open_prompt_title">Auto-open suggestie</string>
<string name="biometric_auto_open_prompt_summary">Automatisch om geavanceerde ontgrendeling vragen als een database hiervoor is ingesteld</string>
<string name="enable">Inschakelen</string>
<string name="disable">Uitschakelen</string>
<string name="master_key">Hoofdsleutel</string>
@@ -390,7 +390,7 @@
<string name="contains_duplicate_uuid">De database bevat dubbele UUID\'s.</string>
<string name="contains_duplicate_uuid_procedure">Probleem oplossen door nieuwe UUID\'s te genereren voor de duplicaten\?</string>
<string name="database_opened">Database geopend</string>
<string name="clipboard_explanation_summary">Kopieer invoervelden met behulp van het klembord van uw apparaat</string>
<string name="clipboard_explanation_summary">Kopieer invoervelden met behulp van het klembord van dit apparaat</string>
<string name="advanced_unlock_explanation_summary">Geavanceerde ontgrendeling gebruiken om een database gemakkelijker te openen</string>
<string name="database_data_compression_title">Gegevenscompressie</string>
<string name="database_data_compression_summary">Gegevenscompressie verkleint de omvang van de database</string>
@@ -413,20 +413,20 @@
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
<string name="education_setup_OTP_title">OTP instellen</string>
<string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string>
<string name="remember_database_locations_title">Databaselocaties onthouden</string>
<string name="remember_database_locations_title">Databaselocaties opslaan</string>
<string name="hide_expired_entries_summary">Verlopen items worden niet getoond</string>
<string name="hide_expired_entries_title">Verberg verlopen items</string>
<string name="download_complete">Klaar!</string>
<string name="hide_expired_entries_title">Verlopen items verbergen</string>
<string name="download_complete">Voltooid!</string>
<string name="download_finalization">Voltooien…</string>
<string name="download_progression">Voortgang: %1$d%%</string>
<string name="download_initialization">Initialiseren…</string>
<string name="download_attachment">Download %1$s</string>
<string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat wordt gevraagd voor tweefactorauthenticatie (2FA).</string>
<string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren voor tweefactorauthenticatie (2FA).</string>
<string name="enable_auto_save_database_title">Automatisch opslaan</string>
<string name="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string>
<string name="autofill_auto_search_title">Automatisch zoeken</string>
<string name="recycle_bin_group_title">Prullenbak</string>
<string name="lock_database_show_button_summary">Geeft de vergrendelknop weer in de gebruikersinterface</string>
<string name="lock_database_show_button_summary">Geef de vergrendelknop weer in de gebruikersinterface</string>
<string name="lock_database_show_button_title">Vergrendelknop weergeven</string>
<string name="autofill_preference_title">Instellingen voor automatisch aanvullen</string>
<string name="keystore_not_accessible">De sleutelopslag is niet correct geïnitialiseerd.</string>
@@ -434,10 +434,10 @@
<string name="warning_database_link_revoked">Toegang tot het bestand ingetrokken door bestandsbeheer</string>
<string name="warning_database_read_only">Bestandstoegang verlenen om databasewijzigingen op te slaan</string>
<string name="command_execution">Opdracht uitvoeren…</string>
<string name="hide_broken_locations_summary">Verberg gebroken links in de lijst met recente databases</string>
<string name="hide_broken_locations_title">Verberg corrupte databasekoppelingen</string>
<string name="remember_keyfile_locations_summary">Onthoudt waar de databasesleutelbestanden zijn opgeslagen</string>
<string name="remember_database_locations_summary">Onthoudt waar de databases zijn opgeslagen</string>
<string name="hide_broken_locations_summary">Gebroken links in de lijst met recente databases verbergen</string>
<string name="hide_broken_locations_title">Corrupte databasekoppelingen verbergen</string>
<string name="remember_keyfile_locations_summary">Onthoud de locatie van databasesleutelbestanden</string>
<string name="remember_database_locations_summary">Onthoud de locatie van databases</string>
<string name="auto_focus_search_summary">Zoekopdracht aanmaken bij het openen van een database</string>
<string name="auto_focus_search_title">Snel zoeken</string>
<string name="menu_delete_entry_history">Geschiedenis wissen</string>
@@ -470,19 +470,19 @@
<string name="content_description_add_item">Item toevoegen</string>
<string name="keyboard_auto_go_action_summary">\"Gaan\"-toetsactie na het indrukken van een \"Veld\"-toets</string>
<string name="keyboard_auto_go_action_title">Automatische toetsactie</string>
<string name="keyboard_previous_fill_in_summary">Schakel automatisch terug naar het vorige toetsenbord na het uitvoeren van de Automatische toetsactie</string>
<string name="keyboard_previous_fill_in_summary">Schakel automatisch terug naar het vorige toetsenbord na het uitvoeren van de \"Automatische toetsactie\"</string>
<string name="keyboard_previous_fill_in_title">Automatische toetsactie</string>
<string name="keyboard_previous_database_credentials_summary">Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm</string>
<string name="keyboard_previous_database_credentials_title">Scherm Databasereferenties</string>
<string name="keyboard_change">Van toetsenbord wisselen</string>
<string name="upload_attachment">%1$s uploaden</string>
<string name="education_add_attachment_summary">Upload een bijlage bij dit item om belangrijke externe gegevens op te slaan.</string>
<string name="upload_attachment">Upload %1$s</string>
<string name="education_add_attachment_summary">Voeg een bijlage toe aan dit item om belangrijke externe gegevens op te slaan.</string>
<string name="education_add_attachment_title">Bijlage toevoegen</string>
<string name="warning_sure_add_file">Toch het bestand toevoegen\?</string>
<string name="warning_file_too_big">Een KeePass-database mag alleen kleine hulpprogramma-bestanden bevatten (zoals PGP-sleutelbestanden).
<string name="warning_sure_add_file">Het bestand toch toevoegen\?</string>
<string name="warning_file_too_big">Een KeePass database is bedoeld om alleen kleine gebruiksbestanden te bevatten (zoals PGP sleutelbestanden).
\n
\nDe database kan erg groot worden en de prestaties kunnen verminderen bij deze upload.</string>
<string name="warning_replace_file">Als je dit bestand uploadt, wordt het bestaande vervangen.</string>
\nMet deze upload kan de database erg groot worden en kunnen de prestaties verminderen.</string>
<string name="warning_replace_file">Uploaden van dit bestand zal het bestaande bestand vervangen.</string>
<string name="content_description_credentials_information">Inloggegevens</string>
<string name="warning_remove_unlinked_attachment">Het verwijderen van niet-gekoppelde gegevens kan de omvang van uw database verkleinen, maar kan ook gegevens verwijderen die voor KeePass-plug-ins worden gebruikt.</string>
<string name="warning_sure_remove_data">Deze gegevens toch verwijderen\?</string>
@@ -505,7 +505,6 @@
<string name="keyboard_save_search_info_summary">Probeer gedeelde informatie op te slaan bij een handmatige invoerselectie</string>
<string name="keyboard_save_search_info_title">Gedeelde info opslaan</string>
<string name="notification">Melding</string>
<string name="crypto_object_not_initialized">Kan crypto-object niet ophalen.</string>
<string name="biometric_security_update_required">Biometrische beveiligingsupdate vereist.</string>
<string name="configure_biometric">Geen biometrische gegevens of apparaatgegevens geregistreerd.</string>
<string name="warning_empty_recycle_bin">Alles definitief uit de prullenbak verwijderen\?</string>
@@ -513,4 +512,35 @@
<string name="save_mode">Veilige modus</string>
<string name="search_mode">Zoekmodus</string>
<string name="error_registration_read_only">Het opslaan van een nieuw item is niet toegestaan in een alleen-lezen database</string>
<string name="education_advanced_unlock_summary">Koppel je wachtwoord aan je gescande biometrische gegevens of apparaatreferentie om je database snel te ontgrendelen.</string>
<string name="education_advanced_unlock_title">Geavanceerde database-ontgrendeling</string>
<string name="enter">Enter</string>
<string name="backspace">Backspace</string>
<string name="select_entry">Item selecteren</string>
<string name="back_to_previous_keyboard">Terug naar vorig toetsenbord</string>
<string name="custom_fields">Aangepaste velden</string>
<string name="advanced_unlock_delete_all_key_warning">Alle coderingssleutels met betrekking tot geavanceerde ontgrendelingsherkenning verwijderen\?</string>
<string name="advanced_unlock_timeout">Time-out voor geavanceerd ontgrendelen</string>
<string name="temp_advanced_unlock_timeout_summary">Duur van geavanceerd ontgrendelingsgebruik voordat de inhoud wordt verwijderd</string>
<string name="temp_advanced_unlock_timeout_title">Vervaltijd voor geavanceerde ontgrendeling</string>
<string name="temp_advanced_unlock_enable_summary">Sla geen versleutelde inhoud op om geavanceerde ontgrendeling te gebruiken</string>
<string name="temp_advanced_unlock_enable_title">Tijdelijke geavanceerde ontgrendeling</string>
<string name="device_credential_unlock_enable_summary">Hiermee kan je de referentie van je apparaat gebruiken om de database te openen</string>
<string name="device_credential_unlock_enable_title">Ontgrendeling met apparaatreferenties</string>
<string name="advanced_unlock_tap_delete">Tik om geavanceerde ontgrendelingstoetsen te verwijderen</string>
<string name="content">Inhoud</string>
<string name="device_credential">Apparaatreferentie</string>
<string name="credential_before_click_advanced_unlock_button">Typ het wachtwoord en klik vervolgens op de knop \"Geavanceerd ontgrendelen\".</string>
<string name="advanced_unlock_prompt_not_initialized">Kan geavanceerde ontgrendelingsprompt niet initialiseren.</string>
<string name="advanced_unlock_scanning_error">Geavanceerde ontgrendelingsfout: %1$s</string>
<string name="advanced_unlock_not_recognized">Kan geavanceerde ontgrendelingsafdruk niet herkennen</string>
<string name="advanced_unlock_invalid_key">Kan de geavanceerde ontgrendelingssleutel niet lezen. Verwijder deze en herhaal de herkenningsprocedure voor het ontgrendelen.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Databasegegevens uitpakken met geavanceerde ontgrendelingsgegevens</string>
<string name="advanced_unlock_prompt_extract_credential_title">Open database met geavanceerde ontgrendelingsherkenning</string>
<string name="advanced_unlock_prompt_store_credential_message">Waarschuwing: je moet nog steeds je hoofdwachtwoord onthouden als je geavanceerde ontgrendelingsherkenning gebruikt.</string>
<string name="advanced_unlock_prompt_store_credential_title">Geavanceerde ontgrendelingsherkenning</string>
<string name="open_advanced_unlock_prompt_store_credential">Open de geavanceerde ontgrendelingsprompt om inloggegevens op te slaan</string>
<string name="open_advanced_unlock_prompt_unlock_database">Open de geavanceerde ontgrendelingsprompt om de database te ontgrendelen</string>
<string name="menu_keystore_remove_key">Geavanceerde ontgrendelingssleutel verwijderen</string>
<string name="error_field_name_already_exists">De veldnaam bestaat al.</string>
</resources>

View File

@@ -228,7 +228,7 @@
<string name="create_keepass_file">Utwórz nową bazę danych</string>
<string name="recycle_bin_title">Wykorzystaj kosz</string>
<string name="recycle_bin_summary">Przenosi grupy i wpisy do grupy \"Kosz\" przed usunięciem</string>
<string name="monospace_font_fields_enable_title">Krój pisma pola</string>
<string name="monospace_font_fields_enable_title">Czcionka pola</string>
<string name="monospace_font_fields_enable_summary">Zmień czcionkę użytą w polach, aby poprawić widoczność postaci</string>
<string name="allow_copy_password_title">Zaufanie do schowka</string>
<string name="allow_copy_password_summary">Zezwalanie na kopiowanie hasła wejściowego i chronionych pól do schowka</string>
@@ -503,7 +503,6 @@
<string name="keyboard_save_search_info_summary">Spróbuj zapisać udostępnione informacje podczas ręcznego wybierania pozycji</string>
<string name="keyboard_save_search_info_title">Zapisz udostępnione informacje</string>
<string name="notification">Powiadomienia</string>
<string name="crypto_object_not_initialized">Nie można pobrać obiektu kryptograficznego.</string>
<string name="biometric_security_update_required">Wymagana aktualizacja zabezpieczeń biometrycznych.</string>
<string name="configure_biometric">Nie zarejestrowano żadnych danych biometrycznych ani danych urządzenia.</string>
<string name="warning_empty_recycle_bin">Trwale usunąć wszystkie węzły z kosza\?</string>
@@ -528,4 +527,12 @@
<string name="advanced_unlock_prompt_store_credential_message">Ostrzeżenie: Jeśli używasz zaawansowanego rozpoznawania odblokowania, nadal musisz zapamiętać hasło główne.</string>
<string name="advanced_unlock_prompt_store_credential_title">Zaawansowane rozpoznawanie odblokowania</string>
<string name="menu_keystore_remove_key">Usuń zaawansowany klucz odblokowujący</string>
<string name="education_advanced_unlock_summary">Połącz swoje hasło ze zeskanowanymi danymi biometrycznymi lub danymi logowania urządzenia, aby szybko odblokować bazę danych.</string>
<string name="education_advanced_unlock_title">Zaawansowane odblokowywanie bazy danych</string>
<string name="advanced_unlock_timeout">Zaawansowany limit czasu odblokowania</string>
<string name="temp_advanced_unlock_timeout_summary">Czas trwania zaawansowanego odblokowywania przed usunięciem jego zawartości</string>
<string name="temp_advanced_unlock_timeout_title">Wygaśnięcie zaawansowanego odblokowania</string>
<string name="temp_advanced_unlock_enable_summary">Nie przechowuj żadnych zaszyfrowanych treści, aby korzystać z zaawansowanego odblokowywania</string>
<string name="advanced_unlock_tap_delete">Naciśnij, aby usunąć zaawansowane klucze odblokowujące</string>
<string name="content">Zawartość</string>
</resources>

View File

@@ -215,12 +215,12 @@
<string name="lock_database_screen_off_title">Блокировка экрана</string>
<string name="lock_database_screen_off_summary">Блокировать базу при отключении экрана</string>
<string name="advanced_unlock">Расширенная разблокировка</string>
<string name="biometric_unlock_enable_title">Сканирование биометрического ключа</string>
<string name="biometric_unlock_enable_title">Биометрическая разблокировка</string>
<string name="biometric_unlock_enable_summary">Включить разблокировку базы при помощи биометрического ключа</string>
<string name="biometric_delete_all_key_title">Удалить ключи шифрования</string>
<string name="biometric_delete_all_key_summary">Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки</string>
<string name="unavailable_feature_text">Невозможно запустить эту функцию.</string>
<string name="unavailable_feature_version">Ваша версия Android %1$s, но требуется %2$s.</string>
<string name="unavailable_feature_text">Невозможно использовать эту функцию.</string>
<string name="unavailable_feature_version">Ваша версия Android %1$s, требуется %2$s.</string>
<string name="unavailable_feature_hardware">Соответствующее оборудование не найдено.</string>
<string name="file_name">Имя файла</string>
<string name="path">Путь</string>
@@ -228,8 +228,8 @@
<string name="create_keepass_file">Создать новую базу</string>
<string name="recycle_bin_title">Использовать \"корзину\"</string>
<string name="recycle_bin_summary">Перемещать группу или запись в \"корзину\" вместо удаления</string>
<string name="monospace_font_fields_enable_title">Шрифт полей</string>
<string name="monospace_font_fields_enable_summary">Использовать в полях особый шрифт для лучшей читаемости</string>
<string name="monospace_font_fields_enable_title">Особый шрифт полей</string>
<string name="monospace_font_fields_enable_summary">Использовать в полях специальный шрифт для лучшей читаемости</string>
<string name="allow_copy_password_title">Доверять буферу обмена</string>
<string name="allow_copy_password_summary">Разрешить копирование пароля и защищённых полей в буфер обмена</string>
<string name="allow_copy_password_warning">Внимание: буфер обмена доступен всем приложениям. Если копируются чувствительные данные, другие программы могут их перехватить.</string>
@@ -494,7 +494,6 @@
<string name="autofill_save_search_info_title">Сохранять данные поиска</string>
<string name="keyboard_save_search_info_summary">Сохранять общую информацию при ручном выборе записи</string>
<string name="keyboard_save_search_info_title">Сохранять общие данные</string>
<string name="crypto_object_not_initialized">Невозможно получить доступ к зашифрованному объекту.</string>
<string name="keyboard_previous_lock_title">Блокировка базы</string>
<string name="keyboard_previous_lock_summary">Автоматически переключаться на предыдущую клавиатуру после блокировки базы</string>
<string name="show_uuid_summary">Показывать UUID, связанный с записью</string>
@@ -513,7 +512,7 @@
<string name="error_registration_read_only">Сохранение новых записей невозможно, т.к. база открыта только для чтения</string>
<string name="error_field_name_already_exists">Поле с таким именем уже существует.</string>
<string name="device_credential_unlock_enable_summary">Позволяет использовать учётные данные вашего устройства для открытия базы</string>
<string name="device_credential_unlock_enable_title">Разблокировка учётных данных устройства</string>
<string name="device_credential_unlock_enable_title">Разблокировка учётными данными устройства</string>
<string name="device_credential">Учётные данные устройства</string>
<string name="credential_before_click_advanced_unlock_button">Введите пароль и нажмите кнопку \"Расширенная разблокировка\".</string>
<string name="advanced_unlock_prompt_not_initialized">Невозможно инициализировать запрос расширенной разблокировки.</string>
@@ -534,10 +533,10 @@
<string name="backspace">Backspace</string>
<string name="select_entry">Выберите запись</string>
<string name="education_advanced_unlock_title">Расширенная разблокировка базы</string>
<string name="advanced_unlock_timeout">Ожидание расширенной разблокировки</string>
<string name="advanced_unlock_timeout">Срок действия расширенной разблокировки</string>
<string name="education_advanced_unlock_summary">Свяжите пароль с отсканированными биометрическими данными или учётными данными устройства, чтобы быстро разблокировать базу.</string>
<string name="temp_advanced_unlock_timeout_summary">Продолжительность использования содержимого расширенной разблокировки до его удаления</string>
<string name="temp_advanced_unlock_timeout_title">Срок действия расширенной разблокировки</string>
<string name="temp_advanced_unlock_timeout_title">Время действия</string>
<string name="temp_advanced_unlock_enable_title">Временная расширенная разблокировка</string>
<string name="temp_advanced_unlock_enable_summary">Не сохранять зашифрованное содержимое для использования расширенной разблокировки</string>
<string name="advanced_unlock_tap_delete">Нажмите, чтобы удалить ключи расширенной разблокировки</string>

View File

@@ -486,7 +486,6 @@
<string name="keyboard_save_search_info_summary">Elle girdi seçimi yaparken paylaşılan bilgileri kaydetmeyi dene</string>
<string name="keyboard_save_search_info_title">Paylaşılan bilgileri kaydet</string>
<string name="notification">Bildirim</string>
<string name="crypto_object_not_initialized">Şifreleme nesnesi alınamıyor.</string>
<string name="biometric_security_update_required">Biyometrik güvenlik güncellemesi gerekli.</string>
<string name="configure_biometric">Biyometrik bilgiler veya aygıt kimlik doğrulama bilgileri kaydedilmedi.</string>
<string name="warning_empty_recycle_bin">Geri dönüşüm kutusundaki tüm düğümler kalıcı olarak silinsin mi\?</string>

View File

@@ -503,7 +503,6 @@
<string name="keyboard_save_search_info_summary">Намагатися зберегти спільні відомості під час вибору запису власноруч</string>
<string name="keyboard_save_search_info_title">Збереження спільних відомостей</string>
<string name="notification">Сповіщення</string>
<string name="crypto_object_not_initialized">Не вдалося отримати крипто-об\'єкт.</string>
<string name="biometric_security_update_required">Необхідно оновити біометричний захист.</string>
<string name="configure_biometric">Біометричних чи облікових даних пристрою не зареєстровано.</string>
<string name="warning_empty_recycle_bin">Видалити всі вузли з кошика остаточно\?</string>

View File

@@ -19,4 +19,5 @@
-->
<resources>
<bool name="biometric_unlock_enable_default" translatable="true">true</bool>
<bool name="device_credential_unlock_enable_default" translatable="true">true</bool>
</resources>

View File

@@ -503,7 +503,6 @@
<string name="keyboard_save_search_info_summary">手动选择条目时,尝试保存共享信息</string>
<string name="keyboard_save_search_info_title">保存分享的信息</string>
<string name="notification">通知</string>
<string name="crypto_object_not_initialized">无法检索加密对象。</string>
<string name="biometric_security_update_required">需要生物识别安全更新。</string>
<string name="configure_biometric">未登记生物识别或设备凭证。</string>
<string name="warning_empty_recycle_bin">从回收站永久删除所有节点?</string>
@@ -533,4 +532,13 @@
<string name="select_entry">选择条目</string>
<string name="back_to_previous_keyboard">回到先前的键盘</string>
<string name="custom_fields">自定义字段</string>
<string name="education_advanced_unlock_summary">将您的密码连接到您扫描的生物特征或设备凭据,以快速解锁您的数据库。</string>
<string name="education_advanced_unlock_title">高级数据库解锁</string>
<string name="advanced_unlock_timeout">高级解锁超时</string>
<string name="temp_advanced_unlock_timeout_summary">删除内容之前高级解锁使用的持续时间</string>
<string name="temp_advanced_unlock_timeout_title">高级解锁过期</string>
<string name="temp_advanced_unlock_enable_summary">不要存储任何加密内容来使用高级解锁</string>
<string name="temp_advanced_unlock_enable_title">临时性高级解锁</string>
<string name="advanced_unlock_tap_delete">点击删除高级解锁密钥</string>
<string name="content">内容</string>
</resources>

View File

@@ -287,7 +287,6 @@
<string name="advanced_unlock_scanning_error">Advanced unlock error: %1$s</string>
<string name="no_credentials_stored">This database does not have stored credential yet.</string>
<string name="advanced_unlock_prompt_not_initialized">Unable to initialize advanced unlock prompt.</string>
<string name="crypto_object_not_initialized">Unable to retrieve crypto object.</string>
<string name="credential_before_click_advanced_unlock_button">Type in the password, and then click the \"Advanced unlock\" button.</string>
<string name="database_history">History</string>
<string name="menu_appearance_settings">Appearance</string>

View File

@@ -0,0 +1,3 @@
* Unlock database by device credentials (PIN/Password/Pattern) with Android M+ #102 #152 #811
* Prevent auto switch back to previous keyboard if otp field exists #814
* Fix timeout reset #817

View File

@@ -0,0 +1,3 @@
* Déverouillage de base de données avec identifiants de l'appareil (PIN/Password/Pattern) pour Android M+ #102 #152 #811
* Empêcher le retour automatique au clavier précédent si le champ otp existe #814
* Correction de la réinitialisation du timer #817