mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
774dddca54 | ||
|
|
de980d030a | ||
|
|
0e859646fe | ||
|
|
059c7b7713 | ||
|
|
5fb7bf71c8 | ||
|
|
8b0133ff7f | ||
|
|
8d834946b8 | ||
|
|
2f646395d4 | ||
|
|
f6e79ba37b | ||
|
|
e633c7a861 | ||
|
|
dc02a8d78c | ||
|
|
baa9b88512 | ||
|
|
c522e87da8 | ||
|
|
ef5ebf2c15 | ||
|
|
4b147e770c | ||
|
|
157a5c0b05 | ||
|
|
f2288b0c64 | ||
|
|
d8506450aa | ||
|
|
f9b085e73f | ||
|
|
388cf6a91b | ||
|
|
9e6e77b363 | ||
|
|
ec33ca8173 | ||
|
|
6be0457947 | ||
|
|
f3b84aa845 | ||
|
|
bd0b5b0954 | ||
|
|
7dc93604ad | ||
|
|
0ab22698a6 | ||
|
|
c885ce7aaf | ||
|
|
92d1a7b901 | ||
|
|
6119054b45 | ||
|
|
e7aed72398 | ||
|
|
cee7fa50f5 | ||
|
|
39a38bb223 | ||
|
|
7159a993db | ||
|
|
23933e80e3 |
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
13
app/src/main/res/layout/fragment_advanced_unlock.xml
Normal file
13
app/src/main/res/layout/fragment_advanced_unlock.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -513,7 +513,6 @@
|
||||
<string name="keyboard_save_search_info_summary">Essayer d’enregistrer les informations partagées lors de la sélection manuelle d’une 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 <strong>open-source</strong> e <strong>senza pubblicità</strong>.
|
||||
\nViene distribuito sotto le condizioni della licenza <strong>GPL versione 3</strong> 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 <strong>nuove funzionalità</strong> e a <strong>correggere errori</strong> 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 <strong>nieuwe functies</strong> te creëren en <strong>problemen op te lossen</strong>.</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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
3
fastlane/metadata/android/en-US/changelogs/49.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/49.txt
Normal 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
|
||||
3
fastlane/metadata/android/fr-FR/changelogs/49.txt
Normal file
3
fastlane/metadata/android/fr-FR/changelogs/49.txt
Normal 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
|
||||
Reference in New Issue
Block a user