diff --git a/CHANGELOG b/CHANGELOG
index 3ab9bac28..e131d31d6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,61 @@
+KeePassDX(2.9.13)
+ * Fix TOTP plugin settings #878
+ * Allow Emoji #796
+ * Scroll and better UI in entry edition screen #876
+
+KeePassDX(2.9.12)
+ * Fix OTP token type #863
+ * Fix auto open biometric prompt #862
+ * Fix back appearance setting #865
+ * Fix orientation change in settings #872
+ * Change memory unit to MiB #851
+ * Small changes #642
+
+KeePassDX(2.9.11)
+ * Add Keyfile XML version 2 (fix hex) #844
+ * Fix hex Keyfile #861
+
+KeePassDX(2.9.10)
+ * Try to fix autofill #852
+ * Fix database change dialog displayed too often #853
+
+KeePassDX(2.9.9)
+ * Detect file changes and reload database #794
+ * Inline suggestions autofill with compatible keyboard (Android R) #827
+ * Add Keyfile XML version 2 #844
+ * Fix binaries of 64 bytes #835
+ * Special search in title fields #830
+ * Priority to OTP button in notifications #845
+ * Fix OTP generation for long secret key #848
+ * Fix small bugs #849
+
+KeePassDX(2.9.8)
+ * Fix specific attachments with kdbx3.1 databases #828
+ * Fix small bugs
+
+KeePassDX(2.9.7)
+ * Remove write permission since Android 10 #823
+ * Fix small bugs
+
+KeePassDX(2.9.6)
+ * Fix KeyFile bug #820
+
+KeePassDX(2.9.5)
+ * Unlock database by device credentials (PIN/Password/Pattern) with Android M+ #102 #152 #811
+ * 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
+
KeePassDX(2.9.3)
* Unlock database by device credentials (PIN/Password/Pattern) #779 #102
+ * Advanced unlock with timeout #102 #437 #566
+ * Remove default database parameter when the file is no longer accessible #803
+ * Move OTP button to the first view level in Magikeyboard #587
+ * Tooltips for Magikeyboard #586
+ * Fix small bugs #805
KeePassDX(2.9.2)
* Managing OTP links from QR applications #556
diff --git a/app/build.gradle b/app/build.gradle
index 513ecacf3..1020de134 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,22 +5,22 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
- buildToolsVersion '30.0.2'
+ buildToolsVersion '30.0.3'
ndkVersion '21.3.6528147'
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 30
- versionCode = 47
- versionName = "2.9.3"
+ versionCode = 57
+ versionName = "2.9.13"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
- manifestPlaceholders = [ googleAndroidBackupAPIKey:"" ]
+ manifestPlaceholders = [ googleAndroidBackupAPIKey:"unused" ]
kapt {
arguments {
@@ -92,7 +92,7 @@ android {
}
}
-def room_version = "2.2.5"
+def room_version = "2.2.6"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@@ -110,6 +110,8 @@ dependencies {
// Database
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
+ // Autofill
+ implementation "androidx.autofill:autofill:1.1.0-rc01"
// Crypto
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
// Time
@@ -121,7 +123,7 @@ dependencies {
// Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.2'
// Apache Commons Codec
- implementation 'commons-codec:commons-codec:1.14'
+ implementation 'commons-codec:commons-codec:1.15'
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fece32d8c..7bcdcb9a3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,10 +14,15 @@
android:name="android.permission.USE_BIOMETRIC" />
+
+ android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="28"
+ tools:ignore="ScopedStorage" />
+
+ android:name="android.permission.QUERY_ALL_PACKAGES"
+ tools:ignore="QueryAllPackagesPermission" />
+ android:windowSoftInputMode="adjustResize" />
+
@@ -207,7 +216,7 @@
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt
index bfa581810..dd1433aed 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt
@@ -26,6 +26,7 @@ import android.content.Intent
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
+import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
@@ -33,6 +34,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.autofill.AutofillHelper
+import com.kunzisoft.keepass.autofill.AutofillHelper.EXTRA_INLINE_SUGGESTIONS_REQUEST
import com.kunzisoft.keepass.autofill.KeeAutofillService
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
@@ -40,7 +42,6 @@ import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION
-import com.kunzisoft.keepass.utils.UriUtil
@RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() {
@@ -84,9 +85,9 @@ class AutofillLauncherActivity : AppCompatActivity() {
private fun launchSelection(searchInfo: SearchInfo) {
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
- val assistStructure = AutofillHelper.retrieveAssistStructure(intent)
+ val autofillComponent = AutofillHelper.retrieveAutofillComponent(intent)
- if (assistStructure == null) {
+ if (autofillComponent == null) {
setResult(Activity.RESULT_CANCELED)
finish()
} else if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId,
@@ -105,21 +106,21 @@ class AutofillLauncherActivity : AppCompatActivity() {
searchInfo,
{ items ->
// Items found
- AutofillHelper.buildResponse(this, items)
+ AutofillHelper.buildResponseAndSetResult(this, items)
finish()
},
{
// Show the database UI to select the entry
GroupActivity.launchForAutofillResult(this,
readOnly,
- assistStructure,
+ autofillComponent,
searchInfo,
false)
},
{
// If database not open
FileDatabaseSelectActivity.launchForAutofillResult(this,
- assistStructure,
+ autofillComponent,
searchInfo)
}
)
@@ -196,7 +197,8 @@ class AutofillLauncherActivity : AppCompatActivity() {
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
fun getAuthIntentSenderForSelection(context: Context,
- searchInfo: SearchInfo? = null): IntentSender {
+ searchInfo: SearchInfo? = null,
+ inlineSuggestionsRequest: InlineSuggestionsRequest? = null): IntentSender {
return PendingIntent.getActivity(context, 0,
// Doesn't work with Parcelable (don't know why?)
Intent(context, AutofillLauncherActivity::class.java).apply {
@@ -205,6 +207,11 @@ class AutofillLauncherActivity : AppCompatActivity() {
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
putExtra(KEY_SEARCH_SCHEME, it.webScheme)
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ inlineSuggestionsRequest?.let {
+ putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
+ }
+ }
},
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
index 8849d26fe..97523d19c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
@@ -39,7 +39,9 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
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.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
@@ -49,20 +51,19 @@ import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
-import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
-import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
+import com.kunzisoft.keepass.otp.OtpEntryFields
+import com.kunzisoft.keepass.services.AttachmentFileNotificationService
+import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper
-import com.kunzisoft.keepass.utils.MenuUtil
-import com.kunzisoft.keepass.utils.UriUtil
-import com.kunzisoft.keepass.utils.createDocument
-import com.kunzisoft.keepass.utils.onCreateDocumentResult
+import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.EntryContentsView
-import com.kunzisoft.keepass.view.showActionError
+import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import java.util.*
import kotlin.collections.HashMap
@@ -133,7 +134,7 @@ class EntryActivity : LockingActivity() {
}
// Focus view to reinitialize timeout
- resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
+ coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
// Init the clipboard helper
clipboardHelper = ClipboardHelper(this)
@@ -150,8 +151,13 @@ class EntryActivity : LockingActivity() {
if (result.isSuccess)
finish()
}
+ ACTION_DATABASE_RELOAD_TASK -> {
+ // Close the current activity
+ this.showActionErrorIfNeeded(result)
+ finish()
+ }
}
- coordinatorLayout?.showActionError(result)
+ coordinatorLayout?.showActionErrorIfNeeded(result)
}
}
@@ -198,8 +204,7 @@ class EntryActivity : LockingActivity() {
// Refresh Menu
invalidateOptionsMenu()
- val entryInfo = entry.getEntryInfo(Database.getInstance())
-
+ val entryInfo = entry.getEntryInfo(mDatabase)
// Manage entry copy to start notification if allowed
if (mFirstLaunchOfActivity) {
// Manage entry to launch copying notification if allowed
@@ -231,23 +236,21 @@ class EntryActivity : LockingActivity() {
private fun fillEntryDataInContentsView(entry: Entry) {
- val database = Database.getInstance()
- database.startManageEntry(entry)
+ val entryInfo = entry.getEntryInfo(mDatabase)
+
// Assign title icon
- titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor)
+ titleIconView?.assignDatabaseIcon(mDatabase!!.drawFactory, entryInfo.icon, iconColor)
// Assign title text
- val entryTitle = entry.title
+ val entryTitle = entryInfo.title
collapsingToolbarLayout?.title = entryTitle
toolbar?.title = entryTitle
// Assign basic fields
- entryContentsView?.assignUserName(entry.username) {
- database.startManageEntry(entry)
- clipboardHelper?.timeoutCopyToClipboard(entry.username,
+ entryContentsView?.assignUserName(entryInfo.username) {
+ clipboardHelper?.timeoutCopyToClipboard(entryInfo.username,
getString(R.string.copy_field,
getString(R.string.entry_user_name)))
- database.stopManageEntry(entry)
}
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
@@ -277,11 +280,9 @@ class EntryActivity : LockingActivity() {
val onPasswordCopyClickListener: View.OnClickListener? = if (allowCopyPasswordAndProtectedFields) {
View.OnClickListener {
- database.startManageEntry(entry)
- clipboardHelper?.timeoutCopyToClipboard(entry.password,
+ clipboardHelper?.timeoutCopyToClipboard(entryInfo.password,
getString(R.string.copy_field,
getString(R.string.entry_password)))
- database.stopManageEntry(entry)
}
} else {
// If dialog not already shown
@@ -291,44 +292,46 @@ class EntryActivity : LockingActivity() {
null
}
}
- entryContentsView?.assignPassword(entry.password,
+ entryContentsView?.assignPassword(entryInfo.password,
allowCopyPasswordAndProtectedFields,
onPasswordCopyClickListener)
//Assign OTP field
- entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
- View.OnClickListener {
- entry.getOtpElement()?.let { otpElement ->
- clipboardHelper?.timeoutCopyToClipboard(
- otpElement.token,
- getString(R.string.copy_field, getString(R.string.entry_otp))
- )
- }
- })
+ entry.getOtpElement()?.let { otpElement ->
+ entryContentsView?.assignOtp(otpElement, entryProgress) {
+ clipboardHelper?.timeoutCopyToClipboard(
+ otpElement.token,
+ getString(R.string.copy_field, getString(R.string.entry_otp))
+ )
+ }
+ }
- entryContentsView?.assignURL(entry.url)
- entryContentsView?.assignNotes(entry.notes)
+ entryContentsView?.assignURL(entryInfo.url)
+ entryContentsView?.assignNotes(entryInfo.notes)
// Assign custom fields
if (mDatabase?.allowEntryCustomFields() == true) {
entryContentsView?.clearExtraFields()
- entry.getExtraFields().forEach { field ->
+ entryInfo.customFields.forEach { field ->
val label = field.name
- val value = field.protectedValue
- val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
- if (allowCopyProtectedField) {
- entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
- clipboardHelper?.timeoutCopyToClipboard(
- value.toString(),
- getString(R.string.copy_field, label)
- )
- }
- } else {
- // If dialog not already shown
- if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
- entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
+ // OTP field is already managed in dedicated view
+ if (label != OtpEntryFields.OTP_TOKEN_FIELD) {
+ val value = field.protectedValue
+ val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
+ if (allowCopyProtectedField) {
+ entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
+ clipboardHelper?.timeoutCopyToClipboard(
+ value.toString(),
+ getString(R.string.copy_field, label)
+ )
+ }
} else {
- entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
+ // If dialog not already shown
+ if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
+ entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
+ } else {
+ entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
+ }
}
}
}
@@ -336,24 +339,16 @@ class EntryActivity : LockingActivity() {
entryContentsView?.setHiddenProtectedValue(!mShowPassword)
// Manage attachments
- mDatabase?.binaryPool?.let { binaryPool ->
- entryContentsView?.assignAttachments(entry.getAttachments(binaryPool).toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
- createDocument(this, attachmentItem.name)?.let { requestCode ->
- mAttachmentsToDownload[requestCode] = attachmentItem
- }
+ entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
+ createDocument(this, attachmentItem.name)?.let { requestCode ->
+ mAttachmentsToDownload[requestCode] = attachmentItem
}
}
// Assign dates
- entryContentsView?.assignCreationDate(entry.creationTime)
- entryContentsView?.assignModificationDate(entry.lastModificationTime)
- entryContentsView?.assignLastAccessDate(entry.lastAccessTime)
- entryContentsView?.setExpires(entry.isCurrentlyExpires)
- if (entry.expires) {
- entryContentsView?.assignExpiresDate(entry.expiryTime)
- } else {
- entryContentsView?.assignExpiresDate(getString(R.string.never))
- }
+ entryContentsView?.assignCreationDate(entryInfo.creationTime)
+ entryContentsView?.assignModificationDate(entryInfo.modificationTime)
+ entryContentsView?.setExpires(entryInfo.expires, entryInfo.expiryTime)
// Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
@@ -368,8 +363,6 @@ class EntryActivity : LockingActivity() {
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
-
- database.stopManageEntry(entry)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -407,6 +400,9 @@ class EntryActivity : LockingActivity() {
menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false
}
+ if (mSpecialMode != SpecialMode.DEFAULT) {
+ menu.findItem(R.id.menu_reload_database)?.isVisible = false
+ }
val gotoUrl = menu.findItem(R.id.menu_goto_url)
gotoUrl?.apply {
@@ -500,6 +496,9 @@ class EntryActivity : LockingActivity() {
R.id.menu_save_database -> {
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
}
+ R.id.menu_reload_database -> {
+ mProgressDatabaseTaskProvider?.startDatabaseReload(false)
+ }
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item)
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
index 698b69c4d..7542d2566 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.activities
import android.app.Activity
import android.app.DatePickerDialog
import android.app.TimePickerDialog
-import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -37,7 +36,6 @@ import android.widget.DatePicker
import android.widget.TimePicker
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
@@ -48,6 +46,8 @@ 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.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage
@@ -56,21 +56,22 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.model.*
-import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
-import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
-import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
+import com.kunzisoft.keepass.services.AttachmentFileNotificationService
+import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
+import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
-import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
+import com.kunzisoft.keepass.view.ToolbarAction
import com.kunzisoft.keepass.view.asError
-import com.kunzisoft.keepass.view.showActionError
+import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.updateLockPaddingLeft
import org.joda.time.DateTime
import java.util.*
@@ -97,7 +98,7 @@ class EntryEditActivity : LockingActivity(),
private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: NestedScrollView? = null
private var entryEditFragment: EntryEditFragment? = null
- private var entryEditAddToolBar: Toolbar? = null
+ private var entryEditAddToolBar: ToolbarAction? = null
private var validateButton: View? = null
private var lockView: View? = null
@@ -117,11 +118,12 @@ class EntryEditActivity : LockingActivity(),
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_entry_edit)
- val toolbar = findViewById(R.id.toolbar)
- setSupportActionBar(toolbar)
- supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp)
+ // Bottom Bar
+ entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
+ setSupportActionBar(entryEditAddToolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
+ supportActionBar?.setDisplayShowTitleEnabled(false)
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
@@ -134,7 +136,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))
@@ -234,51 +236,6 @@ class EntryEditActivity : LockingActivity(),
mTempAttachments = savedInstanceState.getParcelableArrayList(TEMP_ATTACHMENTS) ?: mTempAttachments
}
- // Assign title
- title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry)
-
- // Bottom Bar
- entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
- entryEditAddToolBar?.apply {
- menuInflater.inflate(R.menu.entry_edit, menu)
-
- menu.findItem(R.id.menu_add_field).apply {
- val allowCustomField = mDatabase?.allowEntryCustomFields() == true
- isEnabled = allowCustomField
- isVisible = allowCustomField
- }
-
- // Attachment not compatible below KitKat
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- menu.findItem(R.id.menu_add_attachment).isVisible = false
- }
-
- menu.findItem(R.id.menu_add_otp).apply {
- val allowOTP = mDatabase?.allowOTP == true
- isEnabled = allowOTP
- // OTP not compatible below KitKat
- isVisible = allowOTP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
- }
-
- setOnMenuItemClickListener { item ->
- when (item.itemId) {
- R.id.menu_add_field -> {
- addNewCustomField()
- true
- }
- R.id.menu_add_attachment -> {
- addNewAttachment(item)
- true
- }
- R.id.menu_add_otp -> {
- setupOTP()
- true
- }
- else -> true
- }
- }
- }
-
// To retrieve attachment
mSelectFileHelper = SelectFileHelper(this)
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
@@ -334,8 +291,13 @@ class EntryEditActivity : LockingActivity(),
Log.e(TAG, "Unable to retrieve entry after database action", e)
}
}
+ ACTION_DATABASE_RELOAD_TASK -> {
+ // Close the current activity
+ this.showActionErrorIfNeeded(result)
+ finish()
+ }
}
- coordinatorLayout?.showActionError(result)
+ coordinatorLayout?.showActionErrorIfNeeded(result)
}
}
@@ -360,7 +322,7 @@ class EntryEditActivity : LockingActivity(),
// Build Autofill response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mDatabase?.let { database ->
- AutofillHelper.buildResponse(this@EntryEditActivity,
+ AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
entry.getEntryInfo(database))
}
}
@@ -478,8 +440,12 @@ class EntryEditActivity : LockingActivity(),
}
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
- verifyNameField(newField) {
+ if (oldField.name.equals(newField.name, true)) {
entryEditFragment?.replaceExtraField(oldField, newField)
+ } else {
+ verifyNameField(newField) {
+ entryEditFragment?.replaceExtraField(oldField, newField)
+ }
}
}
@@ -609,18 +575,30 @@ class EntryEditActivity : LockingActivity(),
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
-
- val inflater = menuInflater
- inflater.inflate(R.menu.database, menu)
- // Save database not needed here
- menu.findItem(R.id.menu_save_database)?.isVisible = false
- MenuUtil.contributionMenuInflater(inflater, menu)
-
+ menuInflater.inflate(R.menu.entry_edit, menu)
return true
}
-
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
+
+ menu?.findItem(R.id.menu_add_field)?.apply {
+ val allowCustomField = mDatabase?.allowEntryCustomFields() == true
+ isEnabled = allowCustomField
+ isVisible = allowCustomField
+ }
+
+ // Attachment not compatible below KitKat
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ menu?.findItem(R.id.menu_add_attachment)?.isVisible = false
+ }
+
+ menu?.findItem(R.id.menu_add_otp)?.apply {
+ val allowOTP = mDatabase?.allowOTP == true
+ isEnabled = allowOTP
+ // OTP not compatible below KitKat
+ isVisible = allowOTP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+ }
+
entryEditActivityEducation?.let {
Handler(Looper.getMainLooper()).post { performedNextEducation(it) }
}
@@ -672,11 +650,16 @@ class EntryEditActivity : LockingActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
- R.id.menu_save_database -> {
- mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
+ R.id.menu_add_field -> {
+ addNewCustomField()
+ return true
}
- R.id.menu_contribute -> {
- MenuUtil.onContributionItemSelected(this)
+ R.id.menu_add_attachment -> {
+ addNewAttachment(item)
+ return true
+ }
+ R.id.menu_add_otp -> {
+ setupOTP()
return true
}
android.R.id.home -> {
@@ -908,7 +891,7 @@ class EntryEditActivity : LockingActivity(),
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
- assistStructure: AssistStructure,
+ autofillComponent: AutofillComponent,
group: Group,
searchInfo: SearchInfo? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
@@ -916,7 +899,7 @@ class EntryEditActivity : LockingActivity(),
intent.putExtra(KEY_PARENT, group.nodeId)
AutofillHelper.startActivityForAutofillResult(activity,
intent,
- assistStructure,
+ autofillComponent,
searchInfo)
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditFragment.kt
index 2c5560487..acd3c5c37 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditFragment.kt
@@ -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
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt
index 60058a243..e2c4672c5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt
@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.activities
import android.app.Activity
-import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -36,7 +35,6 @@ import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
-import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
@@ -49,16 +47,18 @@ import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
+import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.KEY_FILE_URI_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.asError
@@ -162,7 +162,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
}
// Observe list of databases
- databaseFilesViewModel.databaseFilesLoaded.observe(this, Observer { databaseFiles ->
+ databaseFilesViewModel.databaseFilesLoaded.observe(this) { databaseFiles ->
when (databaseFiles.databaseFileAction) {
DatabaseFilesViewModel.DatabaseFileAction.NONE -> {
mAdapterDatabaseHistory?.replaceAllDatabaseFileHistoryList(databaseFiles.databaseFileList)
@@ -186,13 +186,13 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
}
}
databaseFilesViewModel.consumeAction()
- })
+ }
// Observe default database
- databaseFilesViewModel.defaultDatabase.observe(this, Observer {
+ databaseFilesViewModel.defaultDatabase.observe(this) {
// Retrieve settings for default database
mAdapterDatabaseHistory?.setDefaultDatabase(it)
- })
+ }
// Attach the dialog thread to this activity
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
@@ -200,8 +200,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
result.data?.getParcelable(DATABASE_URI_KEY)?.let { databaseUri ->
- val keyFileUri = result.data?.getParcelable(KEY_FILE_URI_KEY)
- databaseFilesViewModel.addDatabaseFile(databaseUri, keyFileUri)
+ val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
+ databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
}
}
ACTION_DATABASE_LOAD_TASK -> {
@@ -237,10 +237,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private fun fileNoFoundAction(e: FileNotFoundException) {
val error = getString(R.string.file_not_found_content)
+ Log.e(TAG, error, e)
coordinatorLayout?.let {
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
}
- Log.e(TAG, error, e)
}
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
@@ -331,9 +331,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
}
- override fun onAssignKeyDialogPositiveClick(
- masterPasswordChecked: Boolean, masterPassword: String?,
- keyFileChecked: Boolean, keyFile: Uri?) {
+ override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
try {
mDatabaseFileUri?.let { databaseUri ->
@@ -341,10 +339,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
// Create the new database
mProgressDatabaseTaskProvider?.startDatabaseCreate(
databaseUri,
- masterPasswordChecked,
- masterPassword,
- keyFileChecked,
- keyFile
+ mainCredential
)
}
} catch (e: Exception) {
@@ -354,11 +349,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
}
}
- override fun onAssignKeyDialogNegativeClick(
- masterPasswordChecked: Boolean, masterPassword: String?,
- keyFileChecked: Boolean, keyFile: Uri?) {
-
- }
+ override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
@@ -435,8 +426,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 {
@@ -502,11 +493,11 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
- assistStructure: AssistStructure,
+ autofillComponent: AutofillComponent,
searchInfo: SearchInfo? = null) {
AutofillHelper.startActivityForAutofillResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java),
- assistStructure,
+ autofillComponent,
searchInfo)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
index b8b1b57cf..48bb26c59 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
@@ -20,7 +20,6 @@ package com.kunzisoft.keepass.activities
import android.app.Activity
import android.app.SearchManager
-import android.app.assist.AssistStructure
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -50,7 +49,9 @@ 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.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
@@ -65,15 +66,16 @@ import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.OLD_NODES_KEY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.OLD_NODES_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
@@ -153,7 +155,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) {
@@ -227,10 +229,10 @@ class GroupActivity : LockingActivity(),
currentGroup, searchInfo)
onLaunchActivitySpecialMode()
},
- { searchInfo, assistStructure ->
+ { searchInfo, autofillComponent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
EntryEditActivity.launchForAutofillResult(this@GroupActivity,
- assistStructure,
+ autofillComponent,
currentGroup, searchInfo)
onLaunchActivitySpecialMode()
} else {
@@ -316,7 +318,12 @@ class GroupActivity : LockingActivity(),
if (result.isSuccess) {
// Rebuild all the list to avoid bug when delete node from sort
- mListNodesFragment?.rebuildList()
+ try {
+ mListNodesFragment?.rebuildList()
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to rebuild the list after deletion")
+ e.printStackTrace()
+ }
// Add trash in views list if it doesn't exists
if (database.isRecycleBinEnabled) {
@@ -336,9 +343,20 @@ class GroupActivity : LockingActivity(),
}
}
}
+ ACTION_DATABASE_RELOAD_TASK -> {
+ // Reload the current activity
+ if (result.isSuccess) {
+ startActivity(intent)
+ finish()
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
+ } else {
+ this.showActionErrorIfNeeded(result)
+ finish()
+ }
+ }
}
- coordinatorLayout?.showActionError(result)
+ coordinatorLayout?.showActionErrorIfNeeded(result)
finishNodeAction()
@@ -659,7 +677,7 @@ class GroupActivity : LockingActivity(),
// Build response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
mDatabase?.let { database ->
- AutofillHelper.buildResponse(this,
+ AutofillHelper.buildResponseAndSetResult(this,
entry.getEntryInfo(database))
}
}
@@ -872,6 +890,8 @@ class GroupActivity : LockingActivity(),
}
if (mSpecialMode == SpecialMode.DEFAULT) {
MenuUtil.defaultMenuInflater(inflater, menu)
+ } else {
+ menu.findItem(R.id.menu_reload_database)?.isVisible = false
}
// Menu for recycle bin
@@ -997,6 +1017,10 @@ class GroupActivity : LockingActivity(),
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
return true
}
+ R.id.menu_reload_database -> {
+ mProgressDatabaseTaskProvider?.startDatabaseReload(false)
+ return true
+ }
R.id.menu_empty_recycle_bin -> {
mCurrentGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements
@@ -1124,7 +1148,16 @@ class GroupActivity : LockingActivity(),
private fun rebuildListNodes() {
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment?
// to refresh fragment
- mListNodesFragment?.rebuildList()
+ try {
+ mListNodesFragment?.rebuildList()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ coordinatorLayout?.let { coordinatorLayout ->
+ Snackbar.make(coordinatorLayout,
+ R.string.error_rebuild_list,
+ Snackbar.LENGTH_LONG).asError().show()
+ }
+ }
mCurrentGroup = mListNodesFragment?.mainGroup
// Remove search in intent
deletePreviousSearchGroup()
@@ -1295,14 +1328,14 @@ class GroupActivity : LockingActivity(),
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
readOnly: Boolean,
- assistStructure: AssistStructure,
+ autofillComponent: AutofillComponent,
searchInfo: SearchInfo? = null,
autoSearch: Boolean = false) {
checkTimeAndBuildIntent(activity, null, readOnly) { intent ->
intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
AutofillHelper.startActivityForAutofillResult(activity,
intent,
- assistStructure,
+ autofillComponent,
searchInfo)
}
}
@@ -1419,21 +1452,21 @@ class GroupActivity : LockingActivity(),
}
)
},
- { searchInfo, assistStructure ->
+ { searchInfo, autofillComponent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SearchHelper.checkAutoSearchInfo(activity,
Database.getInstance(),
searchInfo,
{ items ->
// Response is build
- AutofillHelper.buildResponse(activity, items)
+ AutofillHelper.buildResponseAndSetResult(activity, items)
onValidateSpecialMode()
},
{
// Here no search info found, disable auto search
GroupActivity.launchForAutofillResult(activity,
readOnly,
- assistStructure,
+ autofillComponent,
searchInfo,
false)
onLaunchActivitySpecialMode()
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt
index e5657f5cc..63deba4ef 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt
@@ -22,30 +22,24 @@ package com.kunzisoft.keepass.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.util.Log
+import android.view.*
+import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.view.ActionMode
-
import com.kunzisoft.keepass.R
-import com.kunzisoft.keepass.adapters.NodeAdapter
-import com.kunzisoft.keepass.database.element.SortNodeEnum
-import com.kunzisoft.keepass.database.element.Group
-import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
-import com.kunzisoft.keepass.settings.PreferencesUtil
-import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
+import com.kunzisoft.keepass.activities.stylish.StylishFragment
+import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.database.element.Group
+import com.kunzisoft.keepass.database.element.SortNodeEnum
+import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
+import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
@@ -197,7 +191,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Refresh data
- rebuildList()
+ try {
+ rebuildList()
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to rebuild the list during resume")
+ e.printStackTrace()
+ }
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found "
@@ -209,10 +208,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
+ @Throws(IllegalArgumentException::class)
fun rebuildList() {
// Add elements to the list
mainGroup?.let { mainGroup ->
mAdapter?.apply {
+ // Thrown an exception when sort cannot be performed
rebuildList(mainGroup)
// To visually change the elements
if (PreferencesUtil.APPEARANCE_CHANGED) {
@@ -231,8 +232,13 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Tell the adapter to refresh it's list
- mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
- rebuildList()
+ try {
+ mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
+ rebuildList()
+ } catch (e:Exception) {
+ Log.e(TAG, "Unable to rebuild the list with the sort")
+ e.printStackTrace()
+ }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
index e31f58523..502a06581 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.activities
import android.app.Activity
-import android.app.assist.AssistStructure
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@@ -37,9 +36,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.lifecycle.Observer
+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,33 +48,33 @@ import com.kunzisoft.keepass.activities.helpers.SpecialMode
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.AutofillComponent
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
+import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.KEY_FILE_URI_KEY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
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,12 +84,12 @@ 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()
+ private var mDefaultDatabase: Boolean = false
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
@@ -113,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?) {
@@ -133,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
@@ -160,10 +156,6 @@ open class PasswordActivity : SpecialModeActivity() {
}
})
- enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
- enableOrNotTheConfirmationButton()
- }
-
// If is a view intent
getUriFromIntent(intent)
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
@@ -173,8 +165,31 @@ 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
+ }
+
// Observe database file change
- databaseFileViewModel.databaseFileLoaded.observe(this, Observer { databaseFile ->
+ databaseFileViewModel.databaseFileLoaded.observe(this) { databaseFile ->
// Force read only if the file does not exists
mForceReadOnly = databaseFile?.let {
!it.databaseFileExists
@@ -194,19 +209,14 @@ open class PasswordActivity : SpecialModeActivity() {
filenameView?.text = databaseFile?.databaseAlias ?: ""
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri)
- })
+ }
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
onActionFinish = { actionTask, result ->
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
@@ -220,32 +230,37 @@ open class PasswordActivity : SpecialModeActivity() {
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
- // Relaunch loading if we need to fix UUID
- if (resultException is DuplicateUuidDatabaseException) {
- showLoadDatabaseDuplicateUuidMessage {
+ when (resultException) {
+ is DuplicateUuidDatabaseException -> {
+ // Relaunch loading if we need to fix UUID
+ showLoadDatabaseDuplicateUuidMessage {
- var databaseUri: Uri? = null
- var masterPassword: String? = null
- var keyFileUri: Uri? = null
- var readOnly = true
- var cipherEntity: CipherDatabaseEntity? = null
+ var databaseUri: Uri? = null
+ var mainCredential: MainCredential = MainCredential()
+ var readOnly = true
+ var cipherEntity: CipherDatabaseEntity? = null
- result.data?.let { resultData ->
- databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
- masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
- keyFileUri = resultData.getParcelable(KEY_FILE_URI_KEY)
- readOnly = resultData.getBoolean(READ_ONLY_KEY)
- cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
+ result.data?.let { resultData ->
+ databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
+ mainCredential = resultData.getParcelable(MAIN_CREDENTIAL_KEY) ?: mainCredential
+ readOnly = resultData.getBoolean(READ_ONLY_KEY)
+ cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
+ }
+
+ databaseUri?.let { databaseFileUri ->
+ showProgressDialogAndLoadDatabase(
+ databaseFileUri,
+ mainCredential,
+ readOnly,
+ cipherEntity,
+ true)
+ }
}
-
- databaseUri?.let { databaseFileUri ->
- showProgressDialogAndLoadDatabase(
- databaseFileUri,
- masterPassword,
- keyFileUri,
- readOnly,
- cipherEntity,
- true)
+ }
+ is FileNotFoundDatabaseException -> {
+ // Remove this default database inaccessible
+ if (mDefaultDatabase) {
+ databaseFileViewModel.removeDefaultDatabase()
}
}
}
@@ -277,6 +292,9 @@ open class PasswordActivity : SpecialModeActivity() {
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
}
+ mDatabaseFileUri?.let {
+ databaseFileViewModel.checkIfIsDefaultDatabase(it)
+ }
}
override fun onNewIntent(intent: Intent?) {
@@ -303,6 +321,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) {
@@ -369,48 +414,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()
@@ -462,11 +468,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
@@ -530,8 +531,7 @@ open class PasswordActivity : SpecialModeActivity() {
// Show the progress dialog and load the database
showProgressDialogAndLoadDatabase(
databaseUri,
- password,
- keyFileUri,
+ MainCredential(password, keyFileUri),
readOnly,
cipherDatabaseEntity,
false)
@@ -540,15 +540,13 @@ open class PasswordActivity : SpecialModeActivity() {
}
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
- password: String?,
- keyFile: Uri?,
+ mainCredential: MainCredential,
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) {
mProgressDatabaseTaskProvider?.startDatabaseLoad(
databaseUri,
- password,
- keyFile,
+ mainCredential,
readOnly,
cipherDatabaseEntity,
fixDuplicateUUID
@@ -575,11 +573,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)
@@ -589,13 +582,13 @@ open class PasswordActivity : SpecialModeActivity() {
// Check permission
private fun checkPermission() {
- val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
- val permissions = arrayOf(writePermission)
- if (Build.VERSION.SDK_INT >= 23
+ if (Build.VERSION.SDK_INT in 23..28
&& !readOnly
&& !mPermissionAsked) {
mPermissionAsked = true
// Check self permission to show or not the dialog
+ val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+ val permissions = arrayOf(writePermission)
if (toolbar != null
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
@@ -655,21 +648,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)
+ })
}
}
@@ -691,10 +677,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?.deleteEntryKey()
- }
- else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
+ else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
}
return super.onOptionsItemSelected(item)
@@ -708,6 +691,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)
@@ -728,7 +714,7 @@ open class PasswordActivity : SpecialModeActivity() {
when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews()
- Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
+ Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
}
Activity.RESULT_CANCELED -> {
clearCredentialsViews()
@@ -741,6 +727,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"
@@ -844,13 +832,13 @@ open class PasswordActivity : SpecialModeActivity() {
fun launchForAutofillResult(activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
- assistStructure: AssistStructure,
+ autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
- assistStructure,
+ autofillComponent,
searchInfo)
}
}
@@ -908,11 +896,11 @@ open class PasswordActivity : SpecialModeActivity() {
searchInfo)
onLaunchActivitySpecialMode()
},
- { searchInfo, assistStructure -> // Autofill Selection Action
+ { searchInfo, autofillComponent -> // Autofill Selection Action
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PasswordActivity.launchForAutofillResult(activity,
databaseUri, keyFile,
- assistStructure,
+ autofillComponent,
searchInfo)
onLaunchActivitySpecialMode()
} else {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt
index cabface80..55a7fed9b 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt
@@ -37,6 +37,7 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
@@ -76,10 +77,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
}
interface AssignPasswordDialogListener {
- fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
- keyFileChecked: Boolean, keyFile: Uri?)
- fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean, masterPassword: String?,
- keyFileChecked: Boolean, keyFile: Uri?)
+ fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential)
+ fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential)
}
override fun onAttach(activity: Context) {
@@ -161,17 +160,13 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
}
}
if (!error) {
- mListener?.onAssignKeyDialogPositiveClick(
- passwordCheckBox!!.isChecked, mMasterPassword,
- keyFileCheckBox!!.isChecked, mKeyFile)
+ mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
dismiss()
}
}
val negativeButton = dialog1.getButton(DialogInterface.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
- mListener?.onAssignKeyDialogNegativeClick(
- passwordCheckBox!!.isChecked, mMasterPassword,
- keyFileCheckBox!!.isChecked, mKeyFile)
+ mListener?.onAssignKeyDialogNegativeClick(retrieveMainCredential())
dismiss()
}
}
@@ -183,6 +178,12 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
return super.onCreateDialog(savedInstanceState)
}
+ private inline fun retrieveMainCredential(): MainCredential {
+ val masterPassword = if (passwordCheckBox!!.isChecked) mMasterPassword else null
+ val keyFile = if (keyFileCheckBox!!.isChecked) mKeyFile else null
+ return MainCredential(masterPassword, keyFile)
+ }
+
override fun onResume() {
super.onResume()
@@ -242,9 +243,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
builder.setMessage(R.string.warning_empty_password)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (!verifyKeyFile()) {
- mListener?.onAssignKeyDialogPositiveClick(
- passwordCheckBox!!.isChecked, mMasterPassword,
- keyFileCheckBox!!.isChecked, mKeyFile)
+ mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
this@AssignMasterKeyDialogFragment.dismiss()
}
}
@@ -259,9 +258,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.warning_no_encryption_key)
.setPositiveButton(android.R.string.ok) { _, _ ->
- mListener?.onAssignKeyDialogPositiveClick(
- passwordCheckBox!!.isChecked, mMasterPassword,
- keyFileCheckBox!!.isChecked, mKeyFile)
+ mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
this@AssignMasterKeyDialogFragment.dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseChangedDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseChangedDialogFragment.kt
new file mode 100644
index 000000000..6a83f0df7
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseChangedDialogFragment.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2020 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.activities.dialogs
+
+import android.app.Dialog
+import android.os.Bundle
+import android.text.SpannableStringBuilder
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
+
+
+class DatabaseChangedDialogFragment : DialogFragment() {
+
+ var actionDatabaseListener: ActionDatabaseChangedListener? = null
+
+ override fun onPause() {
+ super.onPause()
+ actionDatabaseListener = null
+ this.dismiss()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ activity?.let { activity ->
+
+ val oldSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(OLD_FILE_DATABASE_INFO)
+ val newSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(NEW_FILE_DATABASE_INFO)
+
+ if (oldSnapFileDatabaseInfo != null && newSnapFileDatabaseInfo != null) {
+ // Use the Builder class for convenient dialog construction
+ val builder = AlertDialog.Builder(activity)
+
+ val stringBuilder = SpannableStringBuilder()
+ if (newSnapFileDatabaseInfo.exists) {
+ stringBuilder.append(getString(R.string.warning_database_info_changed))
+ stringBuilder.append("\n\n" +oldSnapFileDatabaseInfo.toString(activity)
+ + "\n→\n" +
+ newSnapFileDatabaseInfo.toString(activity) + "\n\n")
+ stringBuilder.append(getString(R.string.warning_database_info_changed_options))
+ } else {
+ stringBuilder.append(getString(R.string.warning_database_revoked))
+ }
+ builder.setMessage(stringBuilder)
+ builder.setPositiveButton(android.R.string.ok) { _, _ ->
+ actionDatabaseListener?.validateDatabaseChanged()
+ }
+ return builder.create()
+ }
+ }
+ return super.onCreateDialog(savedInstanceState)
+ }
+
+ interface ActionDatabaseChangedListener {
+ fun validateDatabaseChanged()
+ }
+
+ companion object {
+
+ const val DATABASE_CHANGED_DIALOG_TAG = "databaseChangedDialogFragment"
+ private const val OLD_FILE_DATABASE_INFO = "OLD_FILE_DATABASE_INFO"
+ private const val NEW_FILE_DATABASE_INFO = "NEW_FILE_DATABASE_INFO"
+
+ fun getInstance(oldSnapFileDatabaseInfo: SnapFileDatabaseInfo,
+ newSnapFileDatabaseInfo: SnapFileDatabaseInfo)
+ : DatabaseChangedDialogFragment {
+ val fragment = DatabaseChangedDialogFragment()
+ fragment.arguments = Bundle().apply {
+ putParcelable(OLD_FILE_DATABASE_INFO, oldSnapFileDatabaseInfo)
+ putParcelable(NEW_FILE_DATABASE_INFO, newSnapFileDatabaseInfo)
+ }
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DeleteNodesDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DeleteNodesDialogFragment.kt
index d2cf2a1a5..b278bf815 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DeleteNodesDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DeleteNodesDialogFragment.kt
@@ -27,9 +27,9 @@ import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.node.Node
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
open class DeleteNodesDialogFragment : DialogFragment() {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/EmptyRecycleBinDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/EmptyRecycleBinDialogFragment.kt
index bd5f945d3..542e8dcb9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/EmptyRecycleBinDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/EmptyRecycleBinDialogFragment.kt
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.activities.dialogs
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.node.Node
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
class EmptyRecycleBinDialogFragment : DeleteNodesDialogFragment() {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/PasswordEncodingDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/PasswordEncodingDialogFragment.kt
index 60da08681..e9f033e2d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/PasswordEncodingDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/PasswordEncodingDialogFragment.kt
@@ -26,6 +26,7 @@ import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.model.MainCredential
class PasswordEncodingDialogFragment : DialogFragment() {
@@ -49,10 +50,7 @@ class PasswordEncodingDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val databaseUri: Uri? = savedInstanceState?.getParcelable(DATABASE_URI_KEY)
- val masterPasswordChecked: Boolean = savedInstanceState?.getBoolean(MASTER_PASSWORD_CHECKED_KEY) ?: false
- val masterPassword: String? = savedInstanceState?.getString(MASTER_PASSWORD_KEY)
- val keyFileChecked: Boolean = savedInstanceState?.getBoolean(KEY_FILE_CHECKED_KEY) ?: false
- val keyFile: Uri? = savedInstanceState?.getParcelable(KEY_FILE_URI_KEY)
+ val mainCredential: MainCredential = savedInstanceState?.getParcelable(MAIN_CREDENTIAL) ?: MainCredential()
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
@@ -60,10 +58,7 @@ class PasswordEncodingDialogFragment : DialogFragment() {
builder.setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.onPasswordEncodingValidateListener(
databaseUri,
- masterPasswordChecked,
- masterPassword,
- keyFileChecked,
- keyFile
+ mainCredential
)
}
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
@@ -75,32 +70,20 @@ class PasswordEncodingDialogFragment : DialogFragment() {
interface Listener {
fun onPasswordEncodingValidateListener(databaseUri: Uri?,
- masterPasswordChecked: Boolean,
- masterPassword: String?,
- keyFileChecked: Boolean,
- keyFile: Uri?)
+ mainCredential: MainCredential)
}
companion object {
private const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
- private const val MASTER_PASSWORD_CHECKED_KEY = "MASTER_PASSWORD_CHECKED_KEY"
- private const val MASTER_PASSWORD_KEY = "MASTER_PASSWORD_KEY"
- private const val KEY_FILE_CHECKED_KEY = "KEY_FILE_CHECKED_KEY"
- private const val KEY_FILE_URI_KEY = "KEY_FILE_URI_KEY"
+ private const val MAIN_CREDENTIAL = "MAIN_CREDENTIAL"
fun getInstance(databaseUri: Uri,
- masterPasswordChecked: Boolean,
- masterPassword: String?,
- keyFileChecked: Boolean,
- keyFile: Uri?): SortDialogFragment {
+ mainCredential: MainCredential): SortDialogFragment {
val fragment = SortDialogFragment()
fragment.arguments = Bundle().apply {
putParcelable(DATABASE_URI_KEY, databaseUri)
- putBoolean(MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
- putString(MASTER_PASSWORD_KEY, masterPassword)
- putBoolean(KEY_FILE_CHECKED_KEY, keyFileChecked)
- putParcelable(KEY_FILE_URI_KEY, keyFile)
+ putParcelable(MAIN_CREDENTIAL, mainCredential)
}
return fragment
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt
index ef50393fc..487cd52e1 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt
@@ -29,10 +29,7 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.EditText
-import android.widget.Spinner
+import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
@@ -57,6 +54,7 @@ class SetOTPDialogFragment : DialogFragment() {
private var mOtpElement: OtpElement = OtpElement()
+ private var otpTypeMessage: TextView? = null
private var otpTypeSpinner: Spinner? = null
private var otpTokenTypeSpinner: Spinner? = null
private var otpSecretContainer: TextInputLayout? = null
@@ -74,6 +72,8 @@ class SetOTPDialogFragment : DialogFragment() {
private var totpTokenTypeAdapter: ArrayAdapter? = null
private var hotpTokenTypeAdapter: ArrayAdapter? = null
private var otpAlgorithmAdapter: ArrayAdapter? = null
+ private var mHotpTokenTypeArray: Array? = null
+ private var mTotpTokenTypeArray: Array? = null
private var mManualEvent = false
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
@@ -134,6 +134,7 @@ class SetOTPDialogFragment : DialogFragment() {
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
+ otpTypeMessage = root?.findViewById(R.id.setup_otp_type_message)
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
@@ -183,23 +184,23 @@ class SetOTPDialogFragment : DialogFragment() {
// HOTP / TOTP Type selection
val otpTypeArray = OtpType.values()
- otpTypeAdapter = ArrayAdapter(activity,
+ otpTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, otpTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTypeSpinner?.adapter = otpTypeAdapter
// Otp Token type selection
- val hotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
+ mHotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
hotpTokenTypeAdapter = ArrayAdapter(activity,
- android.R.layout.simple_spinner_item, hotpTokenTypeArray).apply {
+ android.R.layout.simple_spinner_item, mHotpTokenTypeArray!!).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
// Proprietary only on closed and full version
- val totpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
+ mTotpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
totpTokenTypeAdapter = ArrayAdapter(activity,
- android.R.layout.simple_spinner_item, totpTokenTypeArray).apply {
+ android.R.layout.simple_spinner_item, mTotpTokenTypeArray!!).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTokenTypeAdapter = hotpTokenTypeAdapter
@@ -207,7 +208,7 @@ class SetOTPDialogFragment : DialogFragment() {
// OTP Algorithm
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
- otpAlgorithmAdapter = ArrayAdapter(activity,
+ otpAlgorithmAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
@@ -372,24 +373,40 @@ class SetOTPDialogFragment : DialogFragment() {
}
private fun upgradeTokenType() {
+ val tokenType = mOtpElement.tokenType
when (mOtpElement.type) {
OtpType.HOTP -> {
otpPeriodContainer?.visibility = View.GONE
otpCounterContainer?.visibility = View.VISIBLE
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
- otpTokenTypeSpinner?.setSelection(OtpTokenType
- .getHotpTokenTypeValues().indexOf(mOtpElement.tokenType))
+ mHotpTokenTypeArray?.let { otpTokenTypeArray ->
+ defineOtpTokenTypeSpinner(otpTokenTypeArray, tokenType, OtpTokenType.RFC4226)
+ }
}
OtpType.TOTP -> {
otpPeriodContainer?.visibility = View.VISIBLE
otpCounterContainer?.visibility = View.GONE
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
- otpTokenTypeSpinner?.setSelection(OtpTokenType
- .getTotpTokenTypeValues().indexOf(mOtpElement.tokenType))
+ mTotpTokenTypeArray?.let { otpTokenTypeArray ->
+ defineOtpTokenTypeSpinner(otpTokenTypeArray, tokenType, OtpTokenType.RFC6238)
+ }
}
}
}
+ private fun defineOtpTokenTypeSpinner(otpTokenTypeArray: Array,
+ tokenType: OtpTokenType,
+ defaultTokenType: OtpTokenType) {
+ val formTokenType = if (otpTokenTypeArray.contains(tokenType)) {
+ otpTypeMessage?.visibility = View.GONE
+ tokenType
+ } else {
+ otpTypeMessage?.visibility = View.VISIBLE
+ defaultTokenType
+ }
+ otpTokenTypeSpinner?.setSelection(otpTokenTypeArray.indexOf(formTokenType))
+ }
+
private fun upgradeParameters() {
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
.indexOf(mOtpElement.algorithm))
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/helpers/EntrySelectionHelper.kt b/app/src/main/java/com/kunzisoft/keepass/activities/helpers/EntrySelectionHelper.kt
index 23d9b3d51..c9ef7e7e7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/helpers/EntrySelectionHelper.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/helpers/EntrySelectionHelper.kt
@@ -19,10 +19,10 @@
*/
package com.kunzisoft.keepass.activities.helpers
-import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
import android.os.Build
+import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
@@ -106,7 +106,7 @@ object EntrySelectionHelper {
fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- if (AutofillHelper.retrieveAssistStructure(intent) != null)
+ if (AutofillHelper.retrieveAutofillComponent(intent) != null)
return SpecialMode.SELECTION
}
return intent.getSerializableExtra(KEY_SPECIAL_MODE) as SpecialMode?
@@ -119,7 +119,7 @@ object EntrySelectionHelper {
fun retrieveTypeModeFromIntent(intent: Intent): TypeMode {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- if (AutofillHelper.retrieveAssistStructure(intent) != null)
+ if (AutofillHelper.retrieveAutofillComponent(intent) != null)
return TypeMode.AUTOFILL
}
return intent.getSerializableExtra(KEY_TYPE_MODE) as TypeMode? ?: TypeMode.DEFAULT
@@ -136,7 +136,7 @@ object EntrySelectionHelper {
saveAction: (searchInfo: SearchInfo) -> Unit,
keyboardSelectionAction: (searchInfo: SearchInfo?) -> Unit,
autofillSelectionAction: (searchInfo: SearchInfo?,
- assistStructure: AssistStructure) -> Unit,
+ autofillComponent: AutofillComponent) -> Unit,
autofillRegistrationAction: (registerInfo: RegisterInfo?) -> Unit) {
when (retrieveSpecialModeFromIntent(intent)) {
@@ -167,14 +167,14 @@ object EntrySelectionHelper {
}
SpecialMode.SELECTION -> {
val searchInfo: SearchInfo? = retrieveSearchInfoFromIntent(intent)
- var assistStructureInit = false
+ var autofillComponentInit = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- AutofillHelper.retrieveAssistStructure(intent)?.let { assistStructure ->
- autofillSelectionAction.invoke(searchInfo, assistStructure)
- assistStructureInit = true
+ AutofillHelper.retrieveAutofillComponent(intent)?.let { autofillComponent ->
+ autofillSelectionAction.invoke(searchInfo, autofillComponent)
+ autofillComponentInit = true
}
}
- if (!assistStructureInit) {
+ if (!autofillComponentInit) {
if (intent.getSerializableExtra(KEY_SPECIAL_MODE) != null) {
when (retrieveTypeModeFromIntent(intent)) {
TypeMode.DEFAULT -> {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt
index d7f15bdcd..1747edbbe 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt
@@ -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
@@ -59,6 +60,9 @@ abstract class LockingActivity : SpecialModeActivity() {
private set
override fun onCreate(savedInstanceState: Bundle?) {
+
+ mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
+
super.onCreate(savedInstanceState)
if (savedInstanceState != null
@@ -83,8 +87,6 @@ abstract class LockingActivity : SpecialModeActivity() {
}
mExitLock = false
-
- mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -163,35 +165,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 +177,7 @@ abstract class LockingActivity : SpecialModeActivity() {
companion object {
- private const val TAG = "LockingActivity"
+ const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
@@ -215,3 +188,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)
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt
index 91c8e5b2f..bc9ce9365 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt
@@ -18,7 +18,7 @@ import com.kunzisoft.keepass.view.SpecialModeView
abstract class SpecialModeActivity : StylishActivity() {
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
- protected var mTypeMode: TypeMode = TypeMode.DEFAULT
+ private var mTypeMode: TypeMode = TypeMode.DEFAULT
private var mSpecialModeView: SpecialModeView? = null
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/App.kt b/app/src/main/java/com/kunzisoft/keepass/app/App.kt
index 8094648b6..5207b1e51 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/App.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/App.kt
@@ -34,7 +34,7 @@ class App : MultiDexApplication() {
}
override fun onTerminate() {
- Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
+ Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
super.onTerminate()
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt
index 055b536d0..eec183cda 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt
@@ -19,27 +19,96 @@
*/
package com.kunzisoft.keepass.app.database
+import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
import android.net.Uri
+import android.os.IBinder
+import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
+import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.SingletonHolderParameter
+import java.util.*
-class CipherDatabaseAction(applicationContext: Context) {
+class CipherDatabaseAction(context: Context) {
+ private val applicationContext = context.applicationContext
private val cipherDatabaseDao =
AppDatabase
.getDatabase(applicationContext)
.cipherDatabaseDao()
+ // Temp DAO to easily remove content if object no longer in memory
+ private var useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
+
+ private val mIntentAdvancedUnlockService = Intent(applicationContext,
+ AdvancedUnlockNotificationService::class.java)
+ private var mBinder: AdvancedUnlockNotificationService.AdvancedUnlockBinder? = null
+ private var mServiceConnection: ServiceConnection? = null
+
+ private var mDatabaseListeners = LinkedList()
+
+ fun reloadPreferences() {
+ useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
+ }
+
+ @Synchronized
+ private fun attachService(performedAction: () -> Unit) {
+ // Check if a service is currently running else do nothing
+ if (mBinder != null) {
+ performedAction.invoke()
+ } else if (mServiceConnection == null) {
+ mServiceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
+ mBinder = (serviceBinder as AdvancedUnlockNotificationService.AdvancedUnlockBinder)
+ performedAction.invoke()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ mBinder = null
+ mServiceConnection = null
+ mDatabaseListeners.forEach {
+ it.onDatabaseCleared()
+ }
+ }
+ }
+ applicationContext.bindService(mIntentAdvancedUnlockService,
+ mServiceConnection!!,
+ Context.BIND_ABOVE_CLIENT)
+ if (mBinder == null) {
+ applicationContext.startService(mIntentAdvancedUnlockService)
+ }
+ }
+ }
+
+ fun registerDatabaseListener(listener: DatabaseListener) {
+ mDatabaseListeners.add(listener)
+ }
+
+ fun unregisterDatabaseListener(listener: DatabaseListener) {
+ mDatabaseListeners.remove(listener)
+ }
+
+ interface DatabaseListener {
+ fun onDatabaseCleared()
+ }
+
fun getCipherDatabase(databaseUri: Uri,
cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) {
- IOActionTask(
- {
- cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
- },
- {
- cipherDatabaseResultListener.invoke(it)
- }
- ).execute()
+ if (useTempDao) {
+ attachService {
+ cipherDatabaseResultListener.invoke(mBinder?.getCipherDatabase(databaseUri))
+ }
+ } else {
+ IOActionTask(
+ {
+ cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
+ },
+ {
+ cipherDatabaseResultListener.invoke(it)
+ }
+ ).execute()
+ }
}
fun containsCipherDatabase(databaseUri: Uri,
@@ -51,36 +120,52 @@ class CipherDatabaseAction(applicationContext: Context) {
fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity,
cipherDatabaseResultListener: (() -> Unit)? = null) {
- IOActionTask(
- {
- val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
-
- // Update values if element not yet in the database
- if (cipherDatabaseRetrieve == null) {
- cipherDatabaseDao.add(cipherDatabaseEntity)
- } else {
- cipherDatabaseDao.update(cipherDatabaseEntity)
+ if (useTempDao) {
+ attachService {
+ mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
+ cipherDatabaseResultListener?.invoke()
+ }
+ } else {
+ IOActionTask(
+ {
+ val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
+ // Update values if element not yet in the database
+ if (cipherDatabaseRetrieve == null) {
+ cipherDatabaseDao.add(cipherDatabaseEntity)
+ } else {
+ cipherDatabaseDao.update(cipherDatabaseEntity)
+ }
+ },
+ {
+ cipherDatabaseResultListener?.invoke()
}
- },
- {
- cipherDatabaseResultListener?.invoke()
- }
- ).execute()
+ ).execute()
+ }
}
fun deleteByDatabaseUri(databaseUri: Uri,
cipherDatabaseResultListener: (() -> Unit)? = null) {
- IOActionTask(
- {
- cipherDatabaseDao.deleteByDatabaseUri(databaseUri.toString())
- },
- {
- cipherDatabaseResultListener?.invoke()
- }
- ).execute()
+ if (useTempDao) {
+ attachService {
+ mBinder?.deleteByDatabaseUri(databaseUri)
+ cipherDatabaseResultListener?.invoke()
+ }
+ } else {
+ IOActionTask(
+ {
+ cipherDatabaseDao.deleteByDatabaseUri(databaseUri.toString())
+ },
+ {
+ cipherDatabaseResultListener?.invoke()
+ }
+ ).execute()
+ }
}
fun deleteAll() {
+ attachService {
+ mBinder?.deleteAll()
+ }
IOActionTask(
{
cipherDatabaseDao.deleteAll()
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseEntity.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseEntity.kt
index 4222ff5d3..7c51f70ee 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseEntity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseEntity.kt
@@ -43,6 +43,11 @@ data class CipherDatabaseEntity(
parcel.readString()!!,
parcel.readString()!!)
+ fun replaceContent(copy: CipherDatabaseEntity) {
+ this.encryptedValue = copy.encryptedValue
+ this.specParameters = copy.specParameters
+ }
+
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(databaseUri)
parcel.writeString(encryptedValue)
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt
index 6b8c22781..9c7242fb9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt
@@ -47,7 +47,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""),
fileDatabaseInfo.exists,
- fileDatabaseInfo.getModificationString(),
+ fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
},
@@ -90,7 +90,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
fileDatabaseInfo.exists,
- fileDatabaseInfo.getModificationString(),
+ fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
)
@@ -152,7 +152,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistory.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists,
- fileDatabaseInfo.getModificationString(),
+ fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/IOActionTask.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/IOActionTask.kt
index 28bd18ed7..4cc0914d4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/database/IOActionTask.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/database/IOActionTask.kt
@@ -34,7 +34,12 @@ class IOActionTask(
mainScope.launch {
withContext(Dispatchers.IO) {
val asyncResult: Deferred = async {
- action.invoke()
+ try {
+ action.invoke()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ null
+ }
}
withContext(Dispatchers.Main) {
afterActionDatabaseListener?.invoke(asyncResult.await())
diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillComponent.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillComponent.kt
new file mode 100644
index 000000000..2043b9705
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillComponent.kt
@@ -0,0 +1,7 @@
+package com.kunzisoft.keepass.autofill
+
+import android.app.assist.AssistStructure
+import android.view.inputmethod.InlineSuggestionsRequest
+
+data class AutofillComponent(val assistStructure: AssistStructure,
+ val inlineSuggestionsRequest: InlineSuggestionsRequest?)
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt
index 5a1a64d1d..aba7353a8 100644
--- a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt
@@ -19,18 +19,27 @@
*/
package com.kunzisoft.keepass.autofill
+import android.annotation.SuppressLint
import android.app.Activity
+import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
+import android.graphics.BlendMode
+import android.graphics.drawable.Icon
import android.os.Build
import android.service.autofill.Dataset
import android.service.autofill.FillResponse
+import android.service.autofill.InlinePresentation
import android.util.Log
import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue
+import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.RemoteViews
+import android.widget.Toast
import androidx.annotation.RequiresApi
+import androidx.autofill.inline.UiVersions
+import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.core.content.ContextCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
@@ -38,8 +47,11 @@ import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon
+import com.kunzisoft.keepass.icons.createIconFromDatabaseIcon
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
+import com.kunzisoft.keepass.settings.AutofillSettingsActivity
+import com.kunzisoft.keepass.settings.PreferencesUtil
@RequiresApi(api = Build.VERSION_CODES.O)
@@ -47,11 +59,17 @@ object AutofillHelper {
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
- private const val ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
+ private const val EXTRA_ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
+ const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST"
- fun retrieveAssistStructure(intent: Intent?): AssistStructure? {
- intent?.let {
- return it.getParcelableExtra(ASSIST_STRUCTURE)
+ fun retrieveAutofillComponent(intent: Intent?): AutofillComponent? {
+ intent?.getParcelableExtra(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure ->
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ AutofillComponent(assistStructure,
+ intent.getParcelableExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST))
+ } else {
+ AutofillComponent(assistStructure, null)
+ }
}
return null
}
@@ -68,26 +86,10 @@ object AutofillHelper {
return ""
}
- internal fun addHeader(responseBuilder: FillResponse.Builder,
- packageName: String,
- webDomain: String?,
- applicationId: String?) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- if (webDomain != null) {
- responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_web_domain).apply {
- setTextViewText(R.id.autofill_web_domain_text, webDomain)
- })
- } else if (applicationId != null) {
- responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_app_id).apply {
- setTextViewText(R.id.autofill_app_id_text, applicationId)
- })
- }
- }
- }
-
- internal fun buildDataset(context: Context,
+ private fun buildDataset(context: Context,
entryInfo: EntryInfo,
- struct: StructureParser.Result): Dataset? {
+ struct: StructureParser.Result,
+ inlinePresentation: InlinePresentation?): Dataset? {
val title = makeEntryTitle(entryInfo)
val views = newRemoteViews(context, title, entryInfo.icon)
val builder = Dataset.Builder(views)
@@ -100,6 +102,12 @@ object AutofillHelper {
builder.setValue(password, AutofillValue.forText(entryInfo.password))
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ inlinePresentation?.let {
+ builder.setInlinePresentation(it)
+ }
+ }
+
return try {
builder.build()
} catch (e: IllegalArgumentException) {
@@ -108,44 +116,120 @@ object AutofillHelper {
}
}
+ @RequiresApi(Build.VERSION_CODES.R)
+ @SuppressLint("RestrictedApi")
+ private fun buildInlinePresentationForEntry(context: Context,
+ inlineSuggestionsRequest: InlineSuggestionsRequest,
+ positionItem: Int,
+ entryInfo: EntryInfo): InlinePresentation? {
+ val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
+ val maxSuggestion = inlineSuggestionsRequest.maxSuggestionCount
+
+ if (positionItem <= maxSuggestion-1
+ && inlinePresentationSpecs.size > positionItem) {
+ val inlinePresentationSpec = inlinePresentationSpecs[positionItem]
+
+ // Make sure that the IME spec claims support for v1 UI template.
+ val imeStyle = inlinePresentationSpec.style
+ if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
+ return null
+
+ // Build the content for IME UI
+ val pendingIntent = PendingIntent.getActivity(context,
+ 0,
+ Intent(context, AutofillSettingsActivity::class.java),
+ 0)
+ return InlinePresentation(
+ InlineSuggestionUi.newContentBuilder(pendingIntent).apply {
+ setContentDescription(context.getString(R.string.autofill_sign_in_prompt))
+ setTitle(entryInfo.title)
+ setSubtitle(entryInfo.username)
+ setStartIcon(Icon.createWithResource(context, R.mipmap.ic_launcher_round).apply {
+ setTintBlendMode(BlendMode.DST)
+ })
+ buildIconFromEntry(context, entryInfo)?.let { icon ->
+ setEndIcon(icon.apply {
+ setTintBlendMode(BlendMode.DST)
+ })
+ }
+ }.build().slice, inlinePresentationSpec, false)
+ }
+ return null
+ }
+
+ fun buildResponse(context: Context,
+ entriesInfo: List,
+ parseResult: StructureParser.Result,
+ inlineSuggestionsRequest: InlineSuggestionsRequest?): FillResponse {
+ val responseBuilder = FillResponse.Builder()
+ // Add Header
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ val packageName = context.packageName
+ parseResult.webDomain?.let { webDomain ->
+ responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_web_domain).apply {
+ setTextViewText(R.id.autofill_web_domain_text, webDomain)
+ })
+ } ?: kotlin.run {
+ parseResult.applicationId?.let { applicationId ->
+ responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_app_id).apply {
+ setTextViewText(R.id.autofill_app_id_text, applicationId)
+ })
+ }
+ }
+ }
+ // Add inline suggestion for new IME and dataset
+ entriesInfo.forEachIndexed { index, entryInfo ->
+ val inlinePresentation = inlineSuggestionsRequest?.let {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ buildInlinePresentationForEntry(context, inlineSuggestionsRequest, index, entryInfo)
+ } else {
+ null
+ }
+ }
+ responseBuilder.addDataset(buildDataset(context, entryInfo, parseResult, inlinePresentation))
+ }
+ return responseBuilder.build()
+ }
+
/**
* Build the Autofill response for one entry
*/
- fun buildResponse(activity: Activity, entryInfo: EntryInfo) {
- buildResponse(activity, ArrayList().apply { add(entryInfo) })
+ fun buildResponseAndSetResult(activity: Activity, entryInfo: EntryInfo) {
+ buildResponseAndSetResult(activity, ArrayList().apply { add(entryInfo) })
}
/**
* Build the Autofill response for many entry
*/
- fun buildResponse(activity: Activity, entriesInfo: List) {
+ fun buildResponseAndSetResult(activity: Activity, entriesInfo: List) {
if (entriesInfo.isEmpty()) {
activity.setResult(Activity.RESULT_CANCELED)
} else {
var setResultOk = false
- activity.intent?.extras?.let { extras ->
- if (extras.containsKey(ASSIST_STRUCTURE)) {
- activity.intent?.getParcelableExtra(ASSIST_STRUCTURE)?.let { structure ->
- StructureParser(structure).parse()?.let { result ->
- // New Response
- val responseBuilder = FillResponse.Builder()
- entriesInfo.forEach {
- responseBuilder.addDataset(buildDataset(activity, it, result))
- }
- val mReplyIntent = Intent()
- Log.d(activity.javaClass.name, "Successed Autofill auth.")
- mReplyIntent.putExtra(
- AutofillManager.EXTRA_AUTHENTICATION_RESULT,
- responseBuilder.build())
- setResultOk = true
- activity.setResult(Activity.RESULT_OK, mReplyIntent)
+ activity.intent?.getParcelableExtra(EXTRA_ASSIST_STRUCTURE)?.let { structure ->
+ StructureParser(structure).parse()?.let { result ->
+ // New Response
+ val response = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val inlineSuggestionsRequest = activity.intent?.getParcelableExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST)
+ if (inlineSuggestionsRequest != null) {
+ Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show()
}
+ buildResponse(activity, entriesInfo, result, inlineSuggestionsRequest)
+ } else {
+ buildResponse(activity, entriesInfo, result, null)
}
+ val mReplyIntent = Intent()
+ Log.d(activity.javaClass.name, "Successed Autofill auth.")
+ mReplyIntent.putExtra(
+ AutofillManager.EXTRA_AUTHENTICATION_RESULT,
+ response)
+ setResultOk = true
+ activity.setResult(Activity.RESULT_OK, mReplyIntent)
}
- if (!setResultOk) {
- Log.w(activity.javaClass.name, "Failed Autofill auth.")
- activity.setResult(Activity.RESULT_CANCELED)
- }
+ }
+ if (!setResultOk) {
+ Log.w(activity.javaClass.name, "Failed Autofill auth.")
+ activity.setResult(Activity.RESULT_CANCELED)
}
}
}
@@ -155,10 +239,16 @@ object AutofillHelper {
*/
fun startActivityForAutofillResult(activity: Activity,
intent: Intent,
- assistStructure: AssistStructure,
+ autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) {
EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.SELECTION)
- intent.putExtra(ASSIST_STRUCTURE, assistStructure)
+ intent.putExtra(EXTRA_ASSIST_STRUCTURE, autofillComponent.assistStructure)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+ && PreferencesUtil.isAutofillInlineSuggestionsEnable(activity)) {
+ autofillComponent.inlineSuggestionsRequest?.let {
+ intent.putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
+ }
+ }
EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo)
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
}
@@ -192,4 +282,11 @@ object AutofillHelper {
}
return presentation
}
+
+ private fun buildIconFromEntry(context: Context, entryInfo: EntryInfo): Icon? {
+ return createIconFromDatabaseIcon(context,
+ Database.getInstance().drawFactory,
+ entryInfo.icon,
+ ContextCompat.getColor(context, R.color.green))
+ }
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt
index 70801c75c..cdd6e11a6 100644
--- a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt
@@ -19,37 +19,50 @@
*/
package com.kunzisoft.keepass.autofill
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.BlendMode
+import android.graphics.drawable.Icon
import android.os.Build
import android.os.CancellationSignal
import android.service.autofill.*
import android.util.Log
import android.view.autofill.AutofillId
+import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.RemoteViews
import androidx.annotation.RequiresApi
+import androidx.autofill.inline.UiVersions
+import androidx.autofill.inline.v1.InlineSuggestionUi
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
+import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil
-import com.kunzisoft.keepass.utils.UriUtil
import java.util.concurrent.atomic.AtomicBoolean
+
@RequiresApi(api = Build.VERSION_CODES.O)
class KeeAutofillService : AutofillService() {
var applicationIdBlocklist: Set? = null
var webDomainBlocklist: Set? = null
var askToSaveData: Boolean = false
+ var autofillInlineSuggestionsEnabled: Boolean = false
private var mLock = AtomicBoolean()
override fun onCreate() {
super.onCreate()
+ getPreferences()
+ }
+ private fun getPreferences() {
applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this)
webDomainBlocklist = PreferencesUtil.webDomainBlocklist(this)
- askToSaveData = PreferencesUtil.askToSaveAutofillData(this) // TODO apply when changed
+ askToSaveData = PreferencesUtil.askToSaveAutofillData(this)
+ autofillInlineSuggestionsEnabled = PreferencesUtil.isAutofillInlineSuggestionsEnable(this)
}
override fun onFillRequest(request: FillRequest,
@@ -75,7 +88,16 @@ class KeeAutofillService : AutofillService() {
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain ->
searchInfo.webDomain = webDomainWithoutSubDomain
- launchSelection(searchInfo, parseResult, callback)
+ val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+ && autofillInlineSuggestionsEnabled) {
+ request.inlineSuggestionsRequest
+ } else {
+ null
+ }
+ launchSelection(searchInfo,
+ parseResult,
+ inlineSuggestionsRequest,
+ callback)
}
}
}
@@ -84,39 +106,40 @@ class KeeAutofillService : AutofillService() {
private fun launchSelection(searchInfo: SearchInfo,
parseResult: StructureParser.Result,
+ inlineSuggestionsRequest: InlineSuggestionsRequest?,
callback: FillCallback) {
SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
- val responseBuilder = FillResponse.Builder()
- AutofillHelper.addHeader(responseBuilder, packageName,
- parseResult.webDomain, parseResult.applicationId)
- items.forEach {
- responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
- }
- callback.onSuccess(responseBuilder.build())
+ callback.onSuccess(
+ AutofillHelper.buildResponse(this,
+ items, parseResult, inlineSuggestionsRequest)
+ )
},
{
// Show UI if no search result
- showUIForEntrySelection(parseResult, searchInfo, callback)
+ showUIForEntrySelection(parseResult,
+ searchInfo, inlineSuggestionsRequest, callback)
},
{
// Show UI if database not open
- showUIForEntrySelection(parseResult, searchInfo, callback)
+ showUIForEntrySelection(parseResult,
+ searchInfo, inlineSuggestionsRequest, callback)
}
)
}
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
searchInfo: SearchInfo,
+ inlineSuggestionsRequest: InlineSuggestionsRequest?,
callback: FillCallback) {
parseResult.allAutofillIds().let { autofillIds ->
if (autofillIds.isNotEmpty()) {
// If the entire Autofill Response is authenticated, AuthActivity is used
// to generate Response.
val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(this,
- searchInfo)
+ searchInfo, inlineSuggestionsRequest)
val responseBuilder = FillResponse.Builder()
val remoteViewsUnlock: RemoteViews = if (!parseResult.webDomain.isNullOrEmpty()) {
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
@@ -149,7 +172,40 @@ class KeeAutofillService : AutofillService() {
)
}
}
- // Build response
+
+ // Build inline presentation
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+ && autofillInlineSuggestionsEnabled) {
+ var inlinePresentation: InlinePresentation? = null
+ inlineSuggestionsRequest?.let {
+ val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
+ if (inlineSuggestionsRequest.maxSuggestionCount > 0
+ && inlinePresentationSpecs.size > 0) {
+ val inlinePresentationSpec = inlinePresentationSpecs[0]
+
+ // Make sure that the IME spec claims support for v1 UI template.
+ val imeStyle = inlinePresentationSpec.style
+ if (UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1)) {
+ // Build the content for IME UI
+ inlinePresentation = InlinePresentation(
+ InlineSuggestionUi.newContentBuilder(
+ PendingIntent.getActivity(this,
+ 0,
+ Intent(this, AutofillSettingsActivity::class.java),
+ 0)
+ ).apply {
+ setContentDescription(getString(R.string.autofill_sign_in_prompt))
+ setTitle(getString(R.string.autofill_sign_in_prompt))
+ setStartIcon(Icon.createWithResource(this@KeeAutofillService, R.mipmap.ic_launcher_round).apply {
+ setTintBlendMode(BlendMode.DST)
+ })
+ }.build().slice, inlinePresentationSpec, false)
+ }
+ }
+ }
+ // Build response
+ responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation)
+ }
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
callback.onSuccess(responseBuilder.build())
}
@@ -190,6 +246,7 @@ class KeeAutofillService : AutofillService() {
override fun onConnected() {
Log.d(TAG, "onConnected")
+ getPreferences()
}
override fun onDisconnected() {
diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt
index dd6ee4cb4..c93cb4a36 100644
--- a/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt
@@ -33,7 +33,7 @@ import java.util.*
* Parse AssistStructure and guess username and password fields.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
-internal class StructureParser(private val structure: AssistStructure) {
+class StructureParser(private val structure: AssistStructure) {
private var result: Result? = null
private var usernameNeeded = true
@@ -274,7 +274,7 @@ internal class StructureParser(private val structure: AssistStructure) {
}
@RequiresApi(api = Build.VERSION_CODES.O)
- internal class Result {
+ class Result {
var applicationId: String? = null
var webDomain: String? = null
diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockCryptoPrompt.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockCryptoPrompt.kt
new file mode 100644
index 000000000..26e9baff4
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockCryptoPrompt.kt
@@ -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)
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
new file mode 100644
index 000000000..83b77a33b
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2020 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.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.services.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
+ if (keepConnection) {
+ activityResult = ActivityResult(requestCode, resultCode, data)
+ }
+ keepConnection = false
+ }
+
+ 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 {
+ this.mAutoOpenPrompt = autoOpenPrompt
+ connect(databaseUri)
+ }
+ } 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
+ })
+ }
+ }
+ }
+ }
+ }
+
+ @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) {
+ activity?.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("AdvancedUnlockManager 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("AdvancedUnlockManager not initialized")
+ }
+
+ @Synchronized
+ fun initAdvancedUnlockMode() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ mAllowAdvancedUnlockMenu = false
+ try {
+ when (biometricMode) {
+ Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
+ Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
+ Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
+ Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
+ Mode.WAIT_CREDENTIAL -> initWaitData()
+ Mode.STORE_CREDENTIAL -> initEncryptData()
+ Mode.EXTRACT_CREDENTIAL -> initDecryptData()
+ }
+ } catch (e: Exception) {
+ onGenericException(e)
+ }
+ invalidateBiometricMenu()
+ }
+ }
+
+ 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
+ activity?.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) {
+ activity?.runOnUiThread {
+ Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
+ setAdvancedUnlockedMessageView(errString.toString())
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ override fun onAuthenticationFailed() {
+ activity?.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() {
+ activity?.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()
+ }
+ } ?: run {
+ onAuthenticationError(-1, getString(R.string.error_database_uri_null))
+ }
+ }
+ }
+ }
+ }
+
+ 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) {
+ activity?.runOnUiThread {
+ mAdvancedUnlockInfoView?.visibility = if (show)
+ View.VISIBLE
+ else {
+ View.GONE
+ }
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ private fun setAdvancedUnlockedTitleView(textId: Int) {
+ activity?.runOnUiThread {
+ mAdvancedUnlockInfoView?.setTitle(textId)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ private fun setAdvancedUnlockedMessageView(textId: Int) {
+ activity?.runOnUiThread {
+ mAdvancedUnlockInfoView?.setMessage(textId)
+ }
+ }
+
+ private fun setAdvancedUnlockedMessageView(text: CharSequence) {
+ activity?.runOnUiThread {
+ mAdvancedUnlockInfoView?.message = text
+ }
+ }
+
+ fun performEducation(passwordActivityEducation: PasswordActivityEducation,
+ readOnlyEducationPerformed: Boolean,
+ onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
+ onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && !readOnlyEducationPerformed) {
+ val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
+ PreferencesUtil.isAdvancedUnlockEnable(requireContext())
+ && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
+ || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
+ && mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
+ && mAdvancedUnlockInfoView?.unlockIconImageView != null
+ && passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
+ onEducationViewClick,
+ onOuterViewClick)
+ }
+ } catch (ignored: Exception) {}
+ }
+
+ enum class Mode {
+ 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt
similarity index 51%
rename from app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt
rename to app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt
index 65700bfbd..c183bc893 100644
--- a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt
@@ -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)
- deleteEntryKey()
+ 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 deleteEntryKey() {
+ 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,39 +413,51 @@ 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)
}
}
- deleteEntryKey()
+ deleteKeystoreKey()
}
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt
deleted file mode 100644
index daf882b38..000000000
--- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt
+++ /dev/null
@@ -1,409 +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 .
- *
- */
-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.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)
-
- 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)
- }
- }
-
- /**
- * 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())
- }
- Mode.EXTRACT_CREDENTIAL -> {
- // retrieve the encrypted value from preferences
- cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
- it?.encryptedValue?.let { value ->
- biometricUnlockDatabaseHelper?.decryptData(value)
- }
- }
- }
- }
- }
- }
- }
-
- private fun initNotAvailable() {
- showFingerPrintViews(false)
-
- advancedUnlockInfoView?.setIconViewClickListener(false, null)
- }
-
- private fun initSecurityUpdateRequired() {
- showFingerPrintViews(true)
- setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
-
- advancedUnlockInfoView?.setIconViewClickListener(false) {
- context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
- }
- }
-
- private fun initNotConfigured() {
- showFingerPrintViews(true)
- setAdvancedUnlockedTitleView(R.string.configure_biometric)
- setAdvancedUnlockedMessageView("")
-
- advancedUnlockInfoView?.setIconViewClickListener(false) {
- context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
- }
- }
-
- private fun initKeyManagerNotAvailable() {
- showFingerPrintViews(true)
- setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
-
- advancedUnlockInfoView?.setIconViewClickListener(false) {
- context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
- }
- }
-
- 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) {
-
- it?.specParameters?.let { specs ->
- biometricUnlockDatabaseHelper?.initDecryptData(specs) { 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)
- }
- }
- }
- }
- }
- }
-
- @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)
- }
-
- fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) {
- if (mAllowAdvancedUnlockMenu)
- menuInflater.inflate(R.menu.advanced_unlock, menu)
- }
-
- fun deleteEntryKey() {
- allowOpenBiometricPrompt = false
- advancedUnlockInfoView?.setIconViewClickListener(false, null)
- biometricUnlockDatabaseHelper?.closeBiometricPrompt()
- biometricUnlockDatabaseHelper?.deleteEntryKey()
- 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
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/StreamCipherFactory.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/StreamCipherFactory.kt
index d36dcd086..b957a9077 100644
--- a/app/src/main/java/com/kunzisoft/keepass/crypto/StreamCipherFactory.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/crypto/StreamCipherFactory.kt
@@ -29,11 +29,12 @@ object StreamCipherFactory {
private val SALSA_IV = byteArrayOf(0xE8.toByte(), 0x30, 0x09, 0x4B, 0x97.toByte(), 0x20, 0x5D, 0x2A)
- fun getInstance(alg: CrsAlgorithm?, key: ByteArray): StreamCipher? {
+ @Throws(Exception::class)
+ fun getInstance(alg: CrsAlgorithm?, key: ByteArray): StreamCipher {
return when {
alg === CrsAlgorithm.Salsa20 -> getSalsa20(key)
alg === CrsAlgorithm.ChaCha20 -> getChaCha20(key)
- else -> null
+ else -> throw Exception("Invalid random cipher")
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt
index b659dea42..7708e45e3 100644
--- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt
@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
+import androidx.annotation.StringRes
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.UnsignedInt
@@ -27,7 +28,11 @@ import java.io.IOException
import java.security.SecureRandom
import java.util.*
-class Argon2Kdf internal constructor() : KdfEngine() {
+class Argon2Kdf(private val type: Type) : KdfEngine() {
+
+ init {
+ uuid = type.CIPHER_UUID
+ }
override val defaultParameters: KdfParameters
get() {
@@ -45,12 +50,8 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val defaultKeyRounds: Long
get() = DEFAULT_ITERATIONS
- init {
- uuid = CIPHER_UUID
- }
-
override fun getName(resources: Resources): String {
- return resources.getString(R.string.kdf_Argon2)
+ return resources.getString(type.nameId)
}
@Throws(IOException::class)
@@ -72,7 +73,9 @@ class Argon2Kdf internal constructor() : KdfEngine() {
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
- return Argon2Native.transformKey(masterKey,
+ return Argon2Native.transformKey(
+ type,
+ masterKey,
salt,
parallelism,
memory,
@@ -141,9 +144,8 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxParallelism: Long
get() = MAX_PARALLELISM
- companion object {
-
- val CIPHER_UUID: UUID = bytes16ToUuid(
+ enum class Type(val CIPHER_UUID: UUID, @StringRes val nameId: Int) {
+ ARGON2_D(bytes16ToUuid(
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
0x6D.toByte(),
@@ -159,7 +161,27 @@ class Argon2Kdf internal constructor() : KdfEngine() {
0x03.toByte(),
0xE3.toByte(),
0x0A.toByte(),
- 0x0C.toByte()))
+ 0x0C.toByte())), R.string.kdf_Argon2d),
+ ARGON2_ID(bytes16ToUuid(
+ byteArrayOf(0x9E.toByte(),
+ 0x29.toByte(),
+ 0x8B.toByte(),
+ 0x19.toByte(),
+ 0x56.toByte(),
+ 0xDB.toByte(),
+ 0x47.toByte(),
+ 0x73.toByte(),
+ 0xB2.toByte(),
+ 0x3D.toByte(),
+ 0xFC.toByte(),
+ 0x3E.toByte(),
+ 0xC6.toByte(),
+ 0xF0.toByte(),
+ 0xA1.toByte(),
+ 0xE6.toByte())), R.string.kdf_Argon2id);
+ }
+
+ companion object {
private const val PARAM_SALT = "S" // byte[]
private const val PARAM_PARALLELISM = "P" // UInt32
diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Native.java b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Native.java
index 144479df9..e961d6e81 100644
--- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Native.java
+++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Native.java
@@ -26,12 +26,29 @@ import java.io.IOException;
public class Argon2Native {
- public static byte[] transformKey(byte[] password, byte[] salt, UnsignedInt parallelism,
+ enum CType {
+ ARGON2_D(0),
+ ARGON2_I(1),
+ ARGON2_ID(2);
+
+ int cValue = 0;
+
+ CType(int i) {
+ cValue = i;
+ }
+ }
+
+ public static byte[] transformKey(Argon2Kdf.Type type, byte[] password, byte[] salt, UnsignedInt parallelism,
UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
byte[] associatedData, UnsignedInt version) throws IOException {
NativeLib.INSTANCE.init();
+ CType cType = CType.ARGON2_D;
+ if (type.equals(Argon2Kdf.Type.ARGON2_ID))
+ cType = CType.ARGON2_ID;
+
return nTransformMasterKey(
+ cType.cValue,
password,
salt,
parallelism.toKotlinInt(),
@@ -42,7 +59,7 @@ public class Argon2Native {
version.toKotlinInt());
}
- private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism,
+ private static native byte[] nTransformMasterKey(int type, byte[] password, byte[] salt, int parallelism,
int memory, int iterations, byte[] secretKey,
byte[] associatedData, int version) throws IOException;
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfFactory.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfFactory.kt
index 7e70becde..8fb3e7de0 100644
--- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfFactory.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfFactory.kt
@@ -21,5 +21,6 @@ package com.kunzisoft.keepass.crypto.keyDerivation
object KdfFactory {
var aesKdf = AesKdf()
- var argon2Kdf = Argon2Kdf()
+ var argon2dKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_D)
+ var argon2idKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_ID)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt
index c21307375..3bb1ed0f7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt
@@ -24,39 +24,26 @@ import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
open class AssignPasswordInDatabaseRunnable (
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
- withMasterPassword: Boolean,
- masterPassword: String?,
- withKeyFile: Boolean,
- keyFile: Uri?)
+ protected val mMainCredential: MainCredential)
: SaveDatabaseRunnable(context, database, true) {
- private var mMasterPassword: String? = null
- protected var mKeyFileUri: Uri? = null
-
private var mBackupKey: ByteArray? = null
- init {
- if (withMasterPassword)
- this.mMasterPassword = masterPassword
- if (withKeyFile)
- this.mKeyFileUri = keyFile
- }
-
override fun onStartRun() {
// Set key
try {
- // TODO move master key methods
mBackupKey = ByteArray(database.masterKey.size)
System.arraycopy(database.masterKey, 0, mBackupKey!!, 0, mBackupKey!!.size)
- val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFileUri)
- database.retrieveMasterKey(mMasterPassword, uriInputStream)
+ val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mMainCredential.keyFileUri)
+ database.retrieveMasterKey(mMainCredential.masterPassword, uriInputStream)
} catch (e: Exception) {
erase(mBackupKey)
setError(e)
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
index 7b7f90d0b..0b55a1bb7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
@@ -24,21 +24,18 @@ import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil
-import com.kunzisoft.keepass.utils.closeDatabase
class CreateDatabaseRunnable(context: Context,
private val mDatabase: Database,
databaseUri: Uri,
private val databaseName: String,
private val rootName: String,
- withMasterPassword: Boolean,
- masterPassword: String?,
- withKeyFile: Boolean,
- keyFile: Uri?,
+ mainCredential: MainCredential,
private val createDatabaseResult: ((Result) -> Unit)?)
- : AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) {
+ : AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential) {
override fun onStartRun() {
try {
@@ -47,7 +44,7 @@ class CreateDatabaseRunnable(context: Context,
createData(mDatabaseUri, databaseName, rootName)
}
} catch (e: Exception) {
- mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
setError(e)
}
@@ -62,7 +59,7 @@ class CreateDatabaseRunnable(context: Context,
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context.applicationContext)
.addOrUpdateDatabaseUri(mDatabaseUri,
- if (PreferencesUtil.rememberKeyFileLocations(context)) mKeyFileUri else null)
+ if (PreferencesUtil.rememberKeyFileLocations(context)) mMainCredential.keyFileUri else null)
}
// Register the current time to init the lock timer
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
index 5f9d0ce04..5fba6d91a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
@@ -25,19 +25,17 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
-import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtil
-import com.kunzisoft.keepass.utils.closeDatabase
class LoadDatabaseRunnable(private val context: Context,
private val mDatabase: Database,
private val mUri: Uri,
- private val mPass: String?,
- private val mKey: Uri?,
+ private val mMainCredential: MainCredential,
private val mReadonly: Boolean,
private val mCipherEntity: CipherDatabaseEntity?,
private val mFixDuplicateUUID: Boolean,
@@ -47,21 +45,19 @@ class LoadDatabaseRunnable(private val context: Context,
override fun onStartRun() {
// Clear before we load
- mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
}
override fun onActionRun() {
try {
- mDatabase.loadData(mUri, mPass, mKey,
+ mDatabase.loadData(mUri,
+ mMainCredential,
mReadonly,
context.contentResolver,
UriUtil.getBinaryDir(context),
mFixDuplicateUUID,
progressTaskUpdater)
}
- catch (e: DuplicateUuidDatabaseException) {
- setError(e)
- }
catch (e: LoadDatabaseException) {
setError(e)
}
@@ -71,7 +67,7 @@ class LoadDatabaseRunnable(private val context: Context,
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context)
.addOrUpdateDatabaseUri(mUri,
- if (PreferencesUtil.rememberKeyFileLocations(context)) mKey else null)
+ if (PreferencesUtil.rememberKeyFileLocations(context)) mMainCredential.keyFileUri else null)
}
// Register the biometric
@@ -83,7 +79,7 @@ class LoadDatabaseRunnable(private val context: Context,
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
} else {
- mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt
index 279b40e27..2c68513f9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDatabaseTaskProvider.kt
@@ -26,6 +26,8 @@ import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import androidx.fragment.app.FragmentActivity
+import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
+import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.Entry
@@ -35,34 +37,37 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
+import com.kunzisoft.keepass.model.MainCredential
+import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
@@ -84,6 +89,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
private var serviceConnection: ServiceConnection? = null
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
+ private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
@@ -101,6 +107,28 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
}
}
+ private val mActionDatabaseListener = object: DatabaseChangedDialogFragment.ActionDatabaseChangedListener {
+ override fun validateDatabaseChanged() {
+ mBinder?.getService()?.saveDatabaseInfo()
+ }
+ }
+
+ private var databaseInfoListener = object: DatabaseTaskNotificationService.DatabaseInfoListener {
+ override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
+ newDatabaseInfo: SnapFileDatabaseInfo) {
+ if (databaseChangedDialogFragment == null) {
+ databaseChangedDialogFragment = activity.supportFragmentManager
+ .findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
+ databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
+ }
+ if (progressTaskDialogFragment == null) {
+ databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(previousDatabaseInfo, newDatabaseInfo)
+ databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
+ databaseChangedDialogFragment?.show(activity.supportFragmentManager, DATABASE_CHANGED_DIALOG_TAG)
+ }
+ }
+ }
+
private fun startDialog(titleId: Int? = null,
messageId: Int? = null,
warningId: Int? = null) {
@@ -140,11 +168,14 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addActionTaskListener(actionTaskListener)
+ addDatabaseFileInfoListener(databaseInfoListener)
getService().checkAction()
+ getService().checkDatabaseInfo()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
+ mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null
}
@@ -206,6 +237,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
fun unregisterProgressTask() {
stopDialog()
+ mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null
@@ -233,30 +265,22 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
*/
fun startDatabaseCreate(databaseUri: Uri,
- masterPasswordChecked: Boolean,
- masterPassword: String?,
- keyFileChecked: Boolean,
- keyFile: Uri?) {
+ mainCredential: MainCredential) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
- putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
- putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
- putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked)
- putParcelable(DatabaseTaskNotificationService.KEY_FILE_URI_KEY, keyFile)
+ putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
}
, ACTION_DATABASE_CREATE_TASK)
}
fun startDatabaseLoad(databaseUri: Uri,
- masterPassword: String?,
- keyFile: Uri?,
+ mainCredential: MainCredential,
readOnly: Boolean,
cipherEntity: CipherDatabaseEntity?,
fixDuplicateUuid: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
- putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
- putParcelable(DatabaseTaskNotificationService.KEY_FILE_URI_KEY, keyFile)
+ putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
putBoolean(DatabaseTaskNotificationService.READ_ONLY_KEY, readOnly)
putParcelable(DatabaseTaskNotificationService.CIPHER_ENTITY_KEY, cipherEntity)
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
@@ -264,18 +288,19 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
, ACTION_DATABASE_LOAD_TASK)
}
+ fun startDatabaseReload(fixDuplicateUuid: Boolean) {
+ start(Bundle().apply {
+ putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
+ }
+ , ACTION_DATABASE_RELOAD_TASK)
+ }
+
fun startDatabaseAssignPassword(databaseUri: Uri,
- masterPasswordChecked: Boolean,
- masterPassword: String?,
- keyFileChecked: Boolean,
- keyFile: Uri?) {
+ mainCredential: MainCredential) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
- putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
- putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
- putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked)
- putParcelable(DatabaseTaskNotificationService.KEY_FILE_URI_KEY, keyFile)
+ putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
}
, ACTION_DATABASE_ASSIGN_PASSWORD_TASK)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt
new file mode 100644
index 000000000..fb78c1dca
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/ReloadDatabaseRunnable.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.database.action
+
+import android.content.Context
+import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.database.exception.LoadDatabaseException
+import com.kunzisoft.keepass.settings.PreferencesUtil
+import com.kunzisoft.keepass.tasks.ActionRunnable
+import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
+import com.kunzisoft.keepass.utils.UriUtil
+
+class ReloadDatabaseRunnable(private val context: Context,
+ private val mDatabase: Database,
+ private val progressTaskUpdater: ProgressTaskUpdater?,
+ private val mLoadDatabaseResult: ((Result) -> Unit)?)
+ : ActionRunnable() {
+
+ override fun onStartRun() {
+ // Clear before we load
+ mDatabase.clear(UriUtil.getBinaryDir(context))
+ }
+
+ override fun onActionRun() {
+ try {
+ mDatabase.reloadData(context.contentResolver,
+ UriUtil.getBinaryDir(context),
+ progressTaskUpdater)
+ } catch (e: LoadDatabaseException) {
+ setError(e)
+ }
+
+ if (result.isSuccess) {
+ // Register the current time to init the lock timer
+ PreferencesUtil.saveCurrentTime(context)
+ } else {
+ mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
+ }
+ }
+
+ override fun onFinishRun() {
+ mLoadDatabaseResult?.invoke(result)
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
index 444b86ba6..3fea65521 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
@@ -31,10 +31,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
-import com.kunzisoft.keepass.database.exception.DatabaseOutputException
-import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
-import com.kunzisoft.keepass.database.exception.LoadDatabaseException
-import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
+import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
@@ -44,6 +41,7 @@ import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.icons.IconDrawableFactory
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder
@@ -330,29 +328,11 @@ class Database {
}
@Throws(LoadDatabaseException::class)
- fun loadData(uri: Uri, password: String?, keyfile: Uri?,
- readOnly: Boolean,
- contentResolver: ContentResolver,
- cacheDirectory: File,
- fixDuplicateUUID: Boolean,
- progressTaskUpdater: ProgressTaskUpdater?) {
-
- this.fileUri = uri
- isReadOnly = readOnly
- if (uri.scheme == "file") {
- val file = File(uri.path!!)
- isReadOnly = !file.canWrite()
- }
-
- // Pass KeyFile Uri as InputStreams
+ private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
+ openDatabaseKDB: (InputStream) -> DatabaseKDB,
+ openDatabaseKDBX: (InputStream) -> DatabaseKDBX) {
var databaseInputStream: InputStream? = null
- var keyFileInputStream: InputStream? = null
try {
- // Get keyFile inputStream
- keyfile?.let {
- keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
- }
-
// Load Data, pass Uris as InputStreams
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
?: throw IOException("Database input stream cannot be retrieve")
@@ -374,22 +354,10 @@ class Database {
when {
// Header of database KDB
- DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB(
- cacheDirectory,
- fixDuplicateUUID)
- .openDatabase(databaseInputStream,
- password,
- keyFileInputStream,
- progressTaskUpdater))
+ DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(openDatabaseKDB(databaseInputStream))
// Header of database KDBX
- DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX(
- cacheDirectory,
- fixDuplicateUUID)
- .openDatabase(databaseInputStream,
- password,
- keyFileInputStream,
- progressTaskUpdater))
+ DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(openDatabaseKDBX(databaseInputStream))
// Header not recognized
else -> throw SignatureDatabaseException()
@@ -397,14 +365,102 @@ class Database {
this.mSearchHelper = SearchHelper()
loaded = true
-
} catch (e: LoadDatabaseException) {
throw e
} catch (e: Exception) {
+ throw LoadDatabaseException(e)
+ } finally {
+ databaseInputStream?.close()
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ fun loadData(uri: Uri,
+ mainCredential: MainCredential,
+ readOnly: Boolean,
+ contentResolver: ContentResolver,
+ cacheDirectory: File,
+ fixDuplicateUUID: Boolean,
+ progressTaskUpdater: ProgressTaskUpdater?) {
+
+ // Save database URI
+ this.fileUri = uri
+
+ // Check if the file is writable
+ this.isReadOnly = readOnly
+
+ // Pass KeyFile Uri as InputStreams
+ var keyFileInputStream: InputStream? = null
+ try {
+ // Get keyFile inputStream
+ mainCredential.keyFileUri?.let { keyFile ->
+ keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
+ }
+
+ // Read database stream for the first time
+ readDatabaseStream(contentResolver, uri,
+ { databaseInputStream ->
+ DatabaseInputKDB(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ mainCredential.masterPassword,
+ keyFileInputStream,
+ progressTaskUpdater,
+ fixDuplicateUUID)
+ },
+ { databaseInputStream ->
+ DatabaseInputKDBX(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ mainCredential.masterPassword,
+ keyFileInputStream,
+ progressTaskUpdater,
+ fixDuplicateUUID)
+ }
+ )
+ } catch (e: FileNotFoundException) {
+ Log.e(TAG, "Unable to load keyfile", e)
throw FileNotFoundDatabaseException()
+ } catch (e: LoadDatabaseException) {
+ throw e
+ } catch (e: Exception) {
+ throw LoadDatabaseException(e)
} finally {
keyFileInputStream?.close()
- databaseInputStream?.close()
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ fun reloadData(contentResolver: ContentResolver,
+ cacheDirectory: File,
+ progressTaskUpdater: ProgressTaskUpdater?) {
+
+ // Retrieve the stream from the old database URI
+ try {
+ fileUri?.let { oldDatabaseUri ->
+ readDatabaseStream(contentResolver, oldDatabaseUri,
+ { databaseInputStream ->
+ DatabaseInputKDB(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ masterKey,
+ progressTaskUpdater)
+ },
+ { databaseInputStream ->
+ DatabaseInputKDBX(cacheDirectory)
+ .openDatabase(databaseInputStream,
+ masterKey,
+ progressTaskUpdater)
+ }
+ )
+ } ?: run {
+ Log.e(TAG, "Database URI is null, database cannot be reloaded")
+ throw IODatabaseException()
+ }
+ } catch (e: FileNotFoundException) {
+ Log.e(TAG, "Unable to load keyfile", e)
+ throw FileNotFoundDatabaseException()
+ } catch (e: LoadDatabaseException) {
+ throw e
+ } catch (e: Exception) {
+ throw LoadDatabaseException(e)
}
}
@@ -426,7 +482,7 @@ class Database {
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
searchInfoString, SearchParameters().apply {
- searchInTitles = false
+ searchInTitles = true
searchInUserNames = false
searchInPasswords = false
searchInUrls = true
@@ -531,7 +587,7 @@ class Database {
this.fileUri = uri
}
- fun closeAndClear(filesDirectory: File? = null) {
+ fun clear(filesDirectory: File? = null) {
drawFactory.clearCache()
// Delete the cache of the database if present
mDatabaseKDB?.clearCache()
@@ -544,7 +600,10 @@ class Database {
} catch (e: Exception) {
Log.e(TAG, "Unable to clear the directory cache.", e)
}
+ }
+ fun clearAndClose(filesDirectory: File? = null) {
+ clear(filesDirectory)
this.mDatabaseKDB = null
this.mDatabaseKDBX = null
this.fileUri = null
@@ -562,7 +621,9 @@ class Database {
}
}
- fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
+ fun validatePasswordEncoding(mainCredential: MainCredential): Boolean {
+ val password = mainCredential.masterPassword
+ val containsKeyFile = mainCredential.keyFileUri != null
return mDatabaseKDB?.validatePasswordEncoding(password, containsKeyFile)
?: mDatabaseKDBX?.validatePasswordEncoding(password, containsKeyFile)
?: false
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt
index 7733af0a7..b985fbe38 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt
@@ -426,6 +426,8 @@ class Entry : Node, EntryVersionedInterface {
entryInfo.icon = icon
entryInfo.username = username
entryInfo.password = password
+ entryInfo.creationTime = creationTime
+ entryInfo.modificationTime = lastModificationTime
entryInfo.expires = expires
entryInfo.expiryTime = expiryTime
entryInfo.url = url
@@ -456,6 +458,9 @@ class Entry : Node, EntryVersionedInterface {
icon = newEntryInfo.icon
username = newEntryInfo.username
password = newEntryInfo.password
+ // Update date time, creation time stay as is
+ lastModificationTime = DateInstant()
+ lastAccessTime = DateInstant()
expires = newEntryInfo.expires
expiryTime = newEntryInfo.expiryTime
url = newEntryInfo.url
@@ -464,9 +469,6 @@ class Entry : Node, EntryVersionedInterface {
database?.binaryPool?.let { binaryPool ->
addAttachments(binaryPool, newEntryInfo.attachments)
}
- // Update date time
- lastAccessTime = DateInstant()
- lastModificationTime = DateInstant()
database?.stopManageEntry(this)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt
index 3f1461d48..8678e84ce 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt
@@ -163,10 +163,6 @@ class DatabaseKDB : DatabaseVersioned() {
finalKey = messageDigest.digest()
}
- override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
- return null
- }
-
override fun createGroup(): GroupKDB {
return GroupKDB()
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt
index 8c117931b..00c44ccdc 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt
@@ -43,10 +43,12 @@ import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
+import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
+import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.VariantDictionary
+import org.apache.commons.codec.binary.Hex
import org.w3c.dom.Node
-import org.w3c.dom.Text
import java.io.File
import java.io.IOException
import java.io.InputStream
@@ -113,7 +115,8 @@ class DatabaseKDBX : DatabaseVersioned {
init {
kdfList.add(KdfFactory.aesKdf)
- kdfList.add(KdfFactory.argon2Kdf)
+ kdfList.add(KdfFactory.argon2dKdf)
+ kdfList.add(KdfFactory.argon2idKdf)
}
constructor()
@@ -123,6 +126,7 @@ class DatabaseKDBX : DatabaseVersioned {
*/
constructor(databaseName: String, rootName: String) {
name = databaseName
+ kdbxVersion = FILE_VERSION_32_3
val group = createGroup().apply {
title = rootName
icon = iconFactory.folderIcon
@@ -179,7 +183,8 @@ class DatabaseKDBX : DatabaseVersioned {
when (oldCompression) {
CompressionAlgorithm.None -> {
when (newCompression) {
- CompressionAlgorithm.None -> {}
+ CompressionAlgorithm.None -> {
+ }
CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
@@ -197,7 +202,8 @@ class DatabaseKDBX : DatabaseVersioned {
CompressionAlgorithm.None -> {
decompressAllBinaries()
}
- CompressionAlgorithm.GZip -> {}
+ CompressionAlgorithm.GZip -> {
+ }
}
}
}
@@ -377,36 +383,82 @@ class DatabaseKDBX : DatabaseVersioned {
try {
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
} catch (e : ParserConfigurationException) {
- Log.e(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)", e)
+ Log.w(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)")
}
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
val doc = documentBuilder.parse(keyInputStream)
+ var xmlKeyFileVersion = 1F
+
val docElement = doc.documentElement
- if (docElement == null || !docElement.nodeName.equals(RootElementName, ignoreCase = true)) {
+ val keyFileChildNodes = docElement.childNodes
+ // Root node
+ if (docElement == null
+ || !docElement.nodeName.equals(XML_NODE_ROOT_NAME, ignoreCase = true)) {
return null
}
-
- val children = docElement.childNodes
- if (children.length < 2) {
+ if (keyFileChildNodes.length < 2)
return null
- }
-
- for (i in 0 until children.length) {
- val child = children.item(i)
-
- if (child.nodeName.equals(KeyElementName, ignoreCase = true)) {
- val keyChildren = child.childNodes
- for (j in 0 until keyChildren.length) {
- val keyChild = keyChildren.item(j)
- if (keyChild.nodeName.equals(KeyDataElementName, ignoreCase = true)) {
- val children2 = keyChild.childNodes
- for (k in 0 until children2.length) {
- val text = children2.item(k)
- if (text.nodeType == Node.TEXT_NODE) {
- val txt = text as Text
- return Base64.decode(txt.nodeValue, BASE_64_FLAG)
+ for (keyFileChildPosition in 0 until keyFileChildNodes.length) {
+ val keyFileChildNode = keyFileChildNodes.item(keyFileChildPosition)
+ //
+ if (keyFileChildNode.nodeName.equals(XML_NODE_META_NAME, ignoreCase = true)) {
+ val metaChildNodes = keyFileChildNode.childNodes
+ for (metaChildPosition in 0 until metaChildNodes.length) {
+ val metaChildNode = metaChildNodes.item(metaChildPosition)
+ //
+ if (metaChildNode.nodeName.equals(XML_NODE_VERSION_NAME, ignoreCase = true)) {
+ val versionChildNodes = metaChildNode.childNodes
+ for (versionChildPosition in 0 until versionChildNodes.length) {
+ val versionChildNode = versionChildNodes.item(versionChildPosition)
+ if (versionChildNode.nodeType == Node.TEXT_NODE) {
+ val versionText = versionChildNode.textContent.removeSpaceChars()
+ try {
+ xmlKeyFileVersion = versionText.toFloat()
+ Log.i(TAG, "Reading XML KeyFile version : $xmlKeyFileVersion")
+ } catch (e: Exception) {
+ Log.e(TAG, "XML Keyfile version cannot be read : $versionText")
+ }
+ }
+ }
+ }
+ }
+ }
+ //
+ if (keyFileChildNode.nodeName.equals(XML_NODE_KEY_NAME, ignoreCase = true)) {
+ val keyChildNodes = keyFileChildNode.childNodes
+ for (keyChildPosition in 0 until keyChildNodes.length) {
+ val keyChildNode = keyChildNodes.item(keyChildPosition)
+ //
+ if (keyChildNode.nodeName.equals(XML_NODE_DATA_NAME, ignoreCase = true)) {
+ var hashString : String? = null
+ if (keyChildNode.hasAttributes()) {
+ val dataNodeAttributes = keyChildNode.attributes
+ hashString = dataNodeAttributes
+ .getNamedItem(XML_ATTRIBUTE_DATA_HASH).nodeValue
+ }
+ val dataChildNodes = keyChildNode.childNodes
+ for (dataChildPosition in 0 until dataChildNodes.length) {
+ val dataChildNode = dataChildNodes.item(dataChildPosition)
+ if (dataChildNode.nodeType == Node.TEXT_NODE) {
+ val dataString = dataChildNode.textContent.removeSpaceChars()
+ when (xmlKeyFileVersion) {
+ 1F -> {
+ // No hash in KeyFile XML version 1
+ return Base64.decode(dataString, BASE_64_FLAG)
+ }
+ 2F -> {
+ return if (hashString != null
+ && checkKeyFileHash(dataString, hashString)) {
+ Log.i(TAG, "Successful key file hash check.")
+ Hex.decodeHex(dataString.toCharArray())
+ } else {
+ Log.e(TAG, "Unable to check the hash of the key file.")
+ null
+ }
+ }
+ }
}
}
}
@@ -416,10 +468,26 @@ class DatabaseKDBX : DatabaseVersioned {
} catch (e: Exception) {
return null
}
-
return null
}
+ private fun checkKeyFileHash(data: String, hash: String): Boolean {
+ val digest: MessageDigest?
+ var success = false
+ try {
+ digest = MessageDigest.getInstance("SHA-256")
+ digest?.reset()
+ // hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
+ val dataDigest = digest.digest(Hex.decodeHex(data.toCharArray()))
+ .copyOfRange(0, 4)
+ .toHexString()
+ success = dataDigest == hash
+ } catch (e: NoSuchAlgorithmException) {
+ e.printStackTrace()
+ }
+ return success
+ }
+
override fun newGroupId(): NodeIdUUID {
var newId: NodeIdUUID
do {
@@ -633,11 +701,12 @@ class DatabaseKDBX : DatabaseVersioned {
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
private const val DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited
- private const val RootElementName = "KeyFile"
- //private const val MetaElementName = "Meta";
- //private const val VersionElementName = "Version";
- private const val KeyElementName = "Key"
- private const val KeyDataElementName = "Data"
+ private const val XML_NODE_ROOT_NAME = "KeyFile"
+ private const val XML_NODE_META_NAME = "Meta"
+ private const val XML_NODE_VERSION_NAME = "Version"
+ private const val XML_NODE_KEY_NAME = "Key"
+ private const val XML_NODE_DATA_NAME = "Data"
+ private const val XML_ATTRIBUTE_DATA_HASH = "Hash"
const val BASE_64_FLAG = Base64.NO_WRAP
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt
index dd2a0ea35..660434e5f 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt
@@ -27,7 +27,11 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
-import java.io.*
+import org.apache.commons.codec.binary.Hex
+import java.io.ByteArrayInputStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.UnsupportedEncodingException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
@@ -124,42 +128,35 @@ abstract class DatabaseVersioned<
@Throws(IOException::class)
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
- val keyByteArrayOutputStream = ByteArrayOutputStream()
- keyInputStream.copyTo(keyByteArrayOutputStream)
- val keyData = keyByteArrayOutputStream.toByteArray()
+ val keyData = keyInputStream.readBytes()
- val keyByteArrayInputStream = ByteArrayInputStream(keyData)
- val key = loadXmlKeyFile(keyByteArrayInputStream)
- if (key != null) {
- return key
+ // Check XML key file
+ val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
+ if (xmlKeyByteArray != null) {
+ return xmlKeyByteArray
}
- when (keyData.size.toLong()) {
- 32L -> return keyData
- 64L -> try {
- return hexStringToByteArray(String(keyData))
- } catch (e: IndexOutOfBoundsException) {
+ // Check 32 bytes key file
+ when (keyData.size) {
+ 32 -> return keyData
+ 64 -> try {
+ return Hex.decodeHex(String(keyData).toCharArray())
+ } catch (ignoredException: Exception) {
// Key is not base 64, treat it as binary data
}
}
- val messageDigest: MessageDigest
+ // Hash file as binary data
try {
- messageDigest = MessageDigest.getInstance("SHA-256")
+ return MessageDigest.getInstance("SHA-256").digest(keyData)
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported")
}
-
- try {
- messageDigest.update(keyData)
- } catch (e: Exception) {
- println(e.toString())
- }
-
- return messageDigest.digest()
}
- protected abstract fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray?
+ protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
+ return null
+ }
open fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
if (password == null && !containsKeyFile)
@@ -391,16 +388,5 @@ abstract class DatabaseVersioned<
private const val TAG = "DatabaseVersioned"
val UUID_ZERO = UUID(0, 0)
-
- fun hexStringToByteArray(s: String): ByteArray {
- val len = s.length
- val data = ByteArray(len / 2)
- var i = 0
- while (i < len) {
- data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
- i += 2
- }
- return data
- }
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt b/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt
index 594154606..630fbd384 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt
@@ -43,20 +43,12 @@ abstract class DatabaseException : Exception {
}
open class LoadDatabaseException : DatabaseException {
-
@StringRes
override var errorId: Int = R.string.error_load_database
constructor() : super()
constructor(throwable: Throwable) : super(throwable)
}
-class ArcFourDatabaseException : LoadDatabaseException {
- @StringRes
- override var errorId: Int = R.string.error_arc4
- constructor() : super()
- constructor(exception: Throwable) : super(exception)
-}
-
class FileNotFoundDatabaseException : LoadDatabaseException {
@StringRes
override var errorId: Int = R.string.file_not_found_content
@@ -67,7 +59,6 @@ class FileNotFoundDatabaseException : LoadDatabaseException {
class InvalidAlgorithmDatabaseException : LoadDatabaseException {
@StringRes
override var errorId: Int = R.string.invalid_algorithm
-
constructor() : super()
constructor(exception: Throwable) : super(exception)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
index 8da334872..ad819cde5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
@@ -41,6 +41,13 @@ abstract class DatabaseInput>
abstract fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): PwDb
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean = false): PwDb
+
+ @Throws(LoadDatabaseException::class)
+ abstract fun openDatabase(databaseInputStream: InputStream,
+ masterKey: ByteArray,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean = false): PwDb
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
index dd5ac5b6b..f2b67e788 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
@@ -45,8 +45,7 @@ import javax.crypto.spec.SecretKeySpec
/**
* Load a KDB database file.
*/
-class DatabaseInputKDB(cacheDirectory: File,
- private val fixDuplicateUUID: Boolean = false)
+class DatabaseInputKDB(cacheDirectory: File)
: DatabaseInput(cacheDirectory) {
private lateinit var mDatabaseToOpen: DatabaseKDB
@@ -55,7 +54,28 @@ class DatabaseInputKDB(cacheDirectory: File,
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDB {
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDB {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ override fun openDatabase(databaseInputStream: InputStream,
+ masterKey: ByteArray,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDB {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabaseToOpen.masterKey = masterKey
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ private fun openDatabase(databaseInputStream: InputStream,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean,
+ assignMasterKey: (() -> Unit)? = null): DatabaseKDB {
try {
// Load entire file, most of it's encrypted.
@@ -84,7 +104,7 @@ class DatabaseInputKDB(cacheDirectory: File,
mDatabaseToOpen = DatabaseKDB()
mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID
- mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
+ assignMasterKey?.invoke()
// Select algorithm
when {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
index e9e3fa0b8..14276b2a9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
@@ -25,9 +25,10 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
+import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
-import com.kunzisoft.keepass.database.element.Attachment
+import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -37,7 +38,6 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
-import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
@@ -63,8 +63,7 @@ import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import kotlin.math.min
-class DatabaseInputKDBX(cacheDirectory: File,
- private val fixDuplicateUUID: Boolean = false)
+class DatabaseInputKDBX(cacheDirectory: File)
: DatabaseInput(cacheDirectory) {
private var randomStream: StreamCipher? = null
@@ -98,12 +97,30 @@ class DatabaseInputKDBX(cacheDirectory: File,
override fun openDatabase(databaseInputStream: InputStream,
password: String?,
keyInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDBX {
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDBX {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabase.retrieveMasterKey(password, keyInputStream)
+ }
+ }
+ @Throws(LoadDatabaseException::class)
+ override fun openDatabase(databaseInputStream: InputStream,
+ masterKey: ByteArray,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean): DatabaseKDBX {
+ return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
+ mDatabase.masterKey = masterKey
+ }
+ }
+
+ @Throws(LoadDatabaseException::class)
+ private fun openDatabase(databaseInputStream: InputStream,
+ progressTaskUpdater: ProgressTaskUpdater?,
+ fixDuplicateUUID: Boolean,
+ assignMasterKey: (() -> Unit)? = null): DatabaseKDBX {
try {
- // TODO performance
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
-
mDatabase = DatabaseKDBX()
mDatabase.changeDuplicateId = fixDuplicateUUID
@@ -116,9 +133,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
hashOfHeader = headerAndHash.hash
val pbHeader = headerAndHash.header
- mDatabase.retrieveMasterKey(password, keyInputStream)
+ assignMasterKey?.invoke()
mDatabase.makeFinalKey(header.masterSeed)
- // TODO performance
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
val engine: CipherEngine
@@ -185,10 +201,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
loadInnerHeader(inputStreamXml, header)
}
- randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
-
- if (randomStream == null) {
- throw ArcFourDatabaseException()
+ try {
+ randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
+ } catch (e: Exception) {
+ throw LoadDatabaseException(e)
}
readDocumentStreamed(createPullParser(inputStreamXml))
@@ -436,8 +452,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
val strData = readString(xpp)
if (strData.isNotEmpty()) {
customIconData = Base64.decode(strData, BASE_64_FLAG)
- } else {
- assert(false)
}
} else {
readUnknown(xpp)
@@ -958,7 +972,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
// Create empty binary if not retrieved in pool
if (binaryRetrieve == null) {
binaryRetrieve = mDatabase.buildNewBinary(cacheDirectory,
- compression = false, protection = true, binaryPoolId = id)
+ compression = false, protection = false, binaryPoolId = id)
}
return binaryRetrieve
}
@@ -1024,29 +1038,20 @@ class DatabaseInputKDBX(cacheDirectory: File,
return xpp.safeNextText()
}
- @Throws(XmlPullParserException::class, IOException::class)
- private fun readBase64String(xpp: XmlPullParser): ByteArray {
-
- //readNextNode = false;
- Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
- val plainText = ByteArray(data.size)
- randomStream?.processBytes(data, 0, data.size, plainText, 0)
- return plainText
- }
- return ByteArray(0)
- }
@Throws(XmlPullParserException::class, IOException::class)
private fun readProtectedBase64String(xpp: XmlPullParser): ByteArray? {
- //(xpp.getEventType() == XmlPullParser.START_TAG);
-
if (xpp.attributeCount > 0) {
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) {
- return readBase64String(xpp)
+ Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
+ val plainText = ByteArray(data.size)
+ randomStream?.processBytes(data, 0, data.size, plainText, 0)
+ return plainText
+ }
+ return ByteArray(0)
}
}
-
return null
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt
deleted file mode 100644
index 4cb13458d..000000000
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt
+++ /dev/null
@@ -1,25 +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 .
- *
- */
-package com.kunzisoft.keepass.database.file.output
-
-open class DatabaseHeaderOutput {
- var hashOfHeader: ByteArray? = null
- protected set
-}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt
index bd18d8207..de76b662d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt
@@ -40,13 +40,16 @@ import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class)
constructor(private val databaseKDBX: DatabaseKDBX,
private val header: DatabaseHeaderKDBX,
- outputStream: OutputStream) : DatabaseHeaderOutput() {
+ outputStream: OutputStream) {
private val los: LittleEndianDataOutputStream
private val mos: MacOutputStream
private val dos: DigestOutputStream
lateinit var headerHmac: ByteArray
+ var hashOfHeader: ByteArray? = null
+ private set
+
init {
val md: MessageDigest
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt
deleted file mode 100644
index fb3666e90..000000000
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2019 Jeremy Jamet / Kunzisoft.
- *
- * This file is part of KeePassDX.
- *
- * KeePassDroid 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.
- *
- * KeePassDroid 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 KeePassDroid. If not, see .
- *
- */
-package com.kunzisoft.keepass.database.file.output
-
-import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
-import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES
-import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
-import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
-import com.kunzisoft.keepass.stream.readBytes
-import com.kunzisoft.keepass.utils.UnsignedInt
-import java.io.IOException
-import java.io.OutputStream
-import kotlin.experimental.or
-
-class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
- private val header: DatabaseHeaderKDBX,
- outputStream: OutputStream) {
-
- private val dataOutputStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
-
- @Throws(IOException::class)
- fun output() {
- dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID)
- dataOutputStream.writeInt(4)
- if (header.innerRandomStream == null)
- throw IOException("Can't write innerRandomStream")
- dataOutputStream.writeUInt(header.innerRandomStream!!.id)
-
- val streamKeySize = header.innerRandomStreamKey.size
- dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey)
- dataOutputStream.writeInt(streamKeySize)
- dataOutputStream.write(header.innerRandomStreamKey)
-
- database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
- val protectedBinary = keyBinary.binary
- // Force decompression to add binary in header
- protectedBinary.decompress()
- // Write type binary
- dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
- // Write size
- dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length() + 1))
- // Write protected flag
- var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
- if (protectedBinary.isProtected) {
- flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
- }
- dataOutputStream.writeByte(flag)
-
- protectedBinary.getInputDataStream().use { inputStream ->
- inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
- dataOutputStream.write(buffer)
- }
- }
- }
-
- dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
- dataOutputStream.writeInt(0)
- }
-}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt
index 4c0951393..4d456e538 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt
@@ -26,7 +26,7 @@ import java.io.OutputStream
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
-abstract class DatabaseOutput protected constructor(protected var mOS: OutputStream) {
+abstract class DatabaseOutput protected constructor(protected var mOutputStream: OutputStream) {
@Throws(DatabaseOutputException::class)
protected open fun setIVs(header: Header): SecureRandom {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt
index f71345bf8..417865c21 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt
@@ -63,7 +63,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
// and remove any orphaned nodes that are no longer part of the tree hierarchy
sortGroupsForOutput()
- val header = outputHeader(mOS)
+ val header = outputHeader(mOutputStream)
val finalKey = getFinalKey(header)
@@ -85,7 +85,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
cipher.init(Cipher.ENCRYPT_MODE,
SecretKeySpec(finalKey, "AES"),
IvParameterSpec(header.encryptionIV))
- val cos = CipherOutputStream(mOS, cipher)
+ val cos = CipherOutputStream(mOutputStream, cipher)
val bos = BufferedOutputStream(cos)
outputPlanGroupAndEntries(bos)
bos.flush()
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
index 422a0fb49..aa3f40ee4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
@@ -38,7 +38,6 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
-import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
@@ -47,6 +46,7 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.*
+import com.kunzisoft.keepass.utils.UnsignedInt
import org.bouncycastle.crypto.StreamCipher
import org.joda.time.DateTime
import org.xmlpull.v1.XmlSerializer
@@ -58,6 +58,7 @@ import java.util.*
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
+import kotlin.experimental.or
class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@@ -81,20 +82,19 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
throw DatabaseOutputException("No such cipher", e)
}
- header = outputHeader(mOS)
+ header = outputHeader(mOutputStream)
val osPlain: OutputStream
osPlain = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
- val cos = attachStreamEncryptor(header!!, mOS)
+ val cos = attachStreamEncryptor(header!!, mOutputStream)
cos.write(header!!.streamStartBytes)
HashedBlockOutputStream(cos)
} else {
- mOS.write(hashOfHeader!!)
- mOS.write(headerHmac!!)
+ mOutputStream.write(hashOfHeader!!)
+ mOutputStream.write(headerHmac!!)
-
- attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey!!))
+ attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!))
}
val osXml: OutputStream
@@ -105,8 +105,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
}
if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
- val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml)
- ihOut.output()
+ outputInnerHeader(mDatabaseKDBX, header!!, osXml)
}
outputDatabase(osXml)
@@ -122,6 +121,49 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
}
}
+ @Throws(IOException::class)
+ private fun outputInnerHeader(database: DatabaseKDBX,
+ header: DatabaseHeaderKDBX,
+ outputStream: OutputStream) {
+ val dataOutputStream = LittleEndianDataOutputStream(outputStream)
+
+ dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID)
+ dataOutputStream.writeInt(4)
+ if (header.innerRandomStream == null)
+ throw IOException("Can't write innerRandomStream")
+ dataOutputStream.writeUInt(header.innerRandomStream!!.id)
+
+ val streamKeySize = header.innerRandomStreamKey.size
+ dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey)
+ dataOutputStream.writeInt(streamKeySize)
+ dataOutputStream.write(header.innerRandomStreamKey)
+
+ database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
+ val protectedBinary = keyBinary.binary
+ // Force decompression to add binary in header
+ protectedBinary.decompress()
+ // Write type binary
+ dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
+ // Write size
+ dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length() + 1))
+ // Write protected flag
+ var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
+ if (protectedBinary.isProtected) {
+ flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
+ }
+ dataOutputStream.writeByte(flag)
+
+ protectedBinary.getInputDataStream().use { inputStream ->
+ inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
+ dataOutputStream.write(buffer)
+ }
+ }
+ }
+
+ dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
+ dataOutputStream.writeInt(0)
+ }
+
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun outputDatabase(outputStream: OutputStream) {
@@ -282,9 +324,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
}
random.nextBytes(header.innerRandomStreamKey)
- randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
- if (randomStream == null) {
- throw DatabaseOutputException("Invalid random cipher")
+ try {
+ randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
+ } catch (e: Exception) {
+ throw DatabaseOutputException(e)
}
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
@@ -420,41 +463,56 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
}
+ /*
+ // Normally used by a single entry but obsolete because binaries are in meta tag with kdbx3.1-
+ // or in file header with kdbx4
+ // binary.isProtected attribute is not used to create the XML
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
- private fun writeBinary(binary : BinaryAttachment) {
- val binaryLength = binary.length()
- if (binaryLength > 0) {
+ private fun writeEntryBinary(binary : BinaryAttachment) {
+ if (binary.length() > 0) {
if (binary.isProtected) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
-
- binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
- val encoded = ByteArray(buffer.size)
- randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
- val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
- xml.text(charArray, 0, charArray.size)
+ binary.getInputDataStream().use { inputStream ->
+ inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
+ val encoded = ByteArray(buffer.size)
+ randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
+ xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
+ }
}
} else {
- if (binary.isCompressed) {
- xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
- }
// Write the XML
- binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
- val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
- xml.text(charArray, 0, charArray.size)
+ binary.getInputDataStream().use { inputStream ->
+ inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
+ xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
+ }
}
}
}
}
+ */
+ // Only uses with kdbx3.1 to write binaries in meta tag
+ // With kdbx4, don't use this method because binaries are in header file
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
- // Use indexes because necessarily in DatabaseV4 (binary header ref is the order)
+ // Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
- writeBinary(keyBinary.binary)
+ val binary = keyBinary.binary
+ if (binary.length() > 0) {
+ if (binary.isCompressed) {
+ xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
+ }
+ // Write the XML
+ binary.getInputDataStream().use { inputStream ->
+ inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
+ xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
+ }
+ }
+ }
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
}
@@ -523,17 +581,15 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (protect) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
-
- val data = value.toString().toByteArray(charset("UTF-8"))
- val valLength = data.size
-
- if (valLength > 0) {
- val encoded = ByteArray(valLength)
- randomStream!!.processBytes(data, 0, valLength, encoded, 0)
+ val data = value.toString().toByteArray()
+ val dataLength = data.size
+ if (data.isNotEmpty()) {
+ val encoded = ByteArray(dataLength)
+ randomStream!!.processBytes(data, 0, dataLength, encoded, 0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
}
} else {
- xml.text(safeXmlString(value.toString()))
+ xml.text(value.toString())
}
xml.endTag(null, DatabaseKDBXXML.ElemValue)
@@ -662,17 +718,19 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (text.isEmpty()) {
return text
}
-
val stringBuilder = StringBuilder()
- var ch: Char
+ var character: Char
for (element in text) {
- ch = element
+ character = element
+ val hexChar = character.toInt()
if (
- ch.toInt() in 0x20..0xD7FF ||
- ch.toInt() == 0x9 || ch.toInt() == 0xA || ch.toInt() == 0xD ||
- ch.toInt() in 0xE000..0xFFFD
+ hexChar in 0x20..0xD7FF ||
+ hexChar == 0x9 ||
+ hexChar == 0xA ||
+ hexChar == 0xD ||
+ hexChar in 0xE000..0xFFFD
) {
- stringBuilder.append(ch)
+ stringBuilder.append(character)
}
}
return stringBuilder.toString()
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt b/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt
index e74303fbf..d3d3a044e 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt
@@ -86,8 +86,8 @@ class PasswordActivityEducation(activity: Activity)
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
return checkAndPerformedEducation(!isEducationBiometricPerformed(activity),
TapTarget.forView(educationView,
- activity.getString(R.string.education_biometric_title),
- activity.getString(R.string.education_biometric_summary))
+ activity.getString(R.string.education_advanced_unlock_title),
+ activity.getString(R.string.education_advanced_unlock_summary))
.textColorInt(Color.WHITE)
.tintTarget(false)
.cancelable(true),
diff --git a/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt b/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt
index 691273b25..add39ee52 100644
--- a/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt
@@ -26,9 +26,12 @@ import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.os.Build
import android.util.Log
import android.widget.ImageView
import android.widget.RemoteViews
+import androidx.annotation.RequiresApi
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.ImageViewCompat
@@ -87,6 +90,22 @@ class IconDrawableFactory {
remoteViews.setImageViewBitmap(imageId, bitmap)
}
+ /**
+ * Utility method to assign a drawable to a icon and tint it
+ */
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun assignDrawableToIcon(superDrawable: SuperDrawable,
+ tintColor: Int = Color.BLACK): Icon {
+ val bitmap = superDrawable.drawable.toBitmap()
+ // Tint bitmap if it's not a custom icon
+ if (superDrawable.tintable && bitmap.isMutable) {
+ Canvas(bitmap).drawBitmap(bitmap, 0.0F, 0.0F, Paint().apply {
+ colorFilter = PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)
+ })
+ }
+ return Icon.createWithBitmap(bitmap)
+ }
+
/**
* Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed
*/
@@ -309,3 +328,22 @@ fun RemoteViews.assignDatabaseIcon(context: Context,
Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
}
}
+
+@RequiresApi(Build.VERSION_CODES.M)
+fun createIconFromDatabaseIcon(context: Context,
+ iconFactory: IconDrawableFactory,
+ icon: IconImage,
+ tintColor: Int = Color.BLACK): Icon? {
+ try {
+ return iconFactory.assignDrawableToIcon(
+ iconFactory.getIconSuperDrawable(context,
+ icon,
+ 24,
+ true,
+ tintColor),
+ tintColor)
+ } catch (e: Exception) {
+ Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
+ }
+ return null
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/magikeyboard/MagikIME.kt b/app/src/main/java/com/kunzisoft/keepass/magikeyboard/MagikIME.kt
index 0af4a798a..5dc9730be 100644
--- a/app/src/main/java/com/kunzisoft/keepass/magikeyboard/MagikIME.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/magikeyboard/MagikIME.kt
@@ -41,7 +41,8 @@ import com.kunzisoft.keepass.adapters.FieldsAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
-import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
+import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
+import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.*
@@ -243,6 +244,14 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.password, 1)
}
+ val otpFieldExists = entryInfoKey?.containsCustomField(OTP_TOKEN_FIELD) ?: false
+ actionGoAutomatically(!otpFieldExists)
+ }
+ KEY_OTP -> {
+ if (entryInfoKey != null) {
+ currentInputConnection.commitText(
+ entryInfoKey!!.getGeneratedFieldValue(OTP_TOKEN_FIELD), 1)
+ }
actionGoAutomatically()
}
KEY_URL -> {
@@ -254,7 +263,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
KEY_FIELDS -> {
if (entryInfoKey != null) {
fieldsAdapter?.apply {
- setFields(entryInfoKey!!.customFields)
+ setFields(entryInfoKey!!.customFields.filter { it.name != OTP_TOKEN_FIELD})
notifyDataSetChanged()
}
}
@@ -272,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()
}
}
@@ -326,6 +336,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
private const val KEY_ENTRY = 620
private const val KEY_USERNAME = 500
private const val KEY_PASSWORD = 510
+ private const val KEY_OTP = 515
private const val KEY_URL = 520
private const val KEY_FIELDS = 530
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt
index 69c965b2c..73f2ab8f2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt
@@ -39,6 +39,8 @@ class EntryInfo : Parcelable {
var icon: IconImage = IconImageStandard()
var username: String = ""
var password: String = ""
+ var creationTime: DateInstant = DateInstant()
+ var modificationTime: DateInstant = DateInstant()
var expires: Boolean = false
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH
var url: String = ""
@@ -55,6 +57,8 @@ class EntryInfo : Parcelable {
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
username = parcel.readString() ?: username
password = parcel.readString() ?: password
+ creationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: creationTime
+ modificationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: modificationTime
expires = parcel.readInt() != 0
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
url = parcel.readString() ?: url
@@ -74,6 +78,8 @@ class EntryInfo : Parcelable {
parcel.writeParcelable(icon, flags)
parcel.writeString(username)
parcel.writeString(password)
+ parcel.writeParcelable(creationTime, flags)
+ parcel.writeParcelable(modificationTime, flags)
parcel.writeInt(if (expires) 1 else 0)
parcel.writeParcelable(expiryTime, flags)
parcel.writeString(url)
@@ -91,13 +97,13 @@ class EntryInfo : Parcelable {
return customFields.any { !it.protectedValue.isProtected }
}
- fun isAutoGeneratedField(field: Field): Boolean {
- return field.name == OTP_TOKEN_FIELD
+ fun containsCustomField(label: String): Boolean {
+ return customFields.lastOrNull { it.name == label } != null
}
fun getGeneratedFieldValue(label: String): String {
- otpModel?.let {
- if (label == OTP_TOKEN_FIELD) {
+ if (label == OTP_TOKEN_FIELD) {
+ otpModel?.let {
return OtpElement(it).token
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/MainCredential.kt b/app/src/main/java/com/kunzisoft/keepass/model/MainCredential.kt
new file mode 100644
index 000000000..e5ee12c72
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/model/MainCredential.kt
@@ -0,0 +1,32 @@
+package com.kunzisoft.keepass.model
+
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+
+data class MainCredential(var masterPassword: String? = null, var keyFileUri: Uri? = null): Parcelable {
+
+ constructor(parcel: Parcel) : this(
+ parcel.readString(),
+ parcel.readParcelable(Uri::class.java.classLoader)) {
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeString(masterPassword)
+ parcel.writeParcelable(keyFileUri, flags)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): MainCredential {
+ return MainCredential(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/SnapFileDatabaseInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/SnapFileDatabaseInfo.kt
new file mode 100644
index 000000000..6d6bbbbd9
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/model/SnapFileDatabaseInfo.kt
@@ -0,0 +1,82 @@
+package com.kunzisoft.keepass.model
+
+import android.content.Context
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import android.text.format.Formatter
+import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
+import java.text.DateFormat
+import java.util.*
+
+/**
+ * Utility data class to get FileDatabaseInfo at a `t` time
+ */
+data class SnapFileDatabaseInfo(var fileUri: Uri?,
+ var exists: Boolean,
+ var lastModification: Long?,
+ var size: Long?): Parcelable {
+
+ constructor(parcel: Parcel) : this(
+ parcel.readParcelable(Uri::class.java.classLoader),
+ parcel.readByte() != 0.toByte(),
+ parcel.readValue(Long::class.java.classLoader) as? Long,
+ parcel.readValue(Long::class.java.classLoader) as? Long) {
+ }
+
+ fun toString(context: Context): String {
+ val lastModificationString = DateFormat.getDateTimeInstance()
+ .format(Date(lastModification ?: 0))
+ return "$lastModificationString, " +
+ Formatter.formatFileSize(context, size ?: 0)
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(fileUri, flags)
+ parcel.writeByte(if (exists) 1 else 0)
+ parcel.writeValue(lastModification)
+ parcel.writeValue(size)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is SnapFileDatabaseInfo) return false
+
+ if (fileUri != other.fileUri) return false
+ if (exists != other.exists) return false
+ if (lastModification != other.lastModification) return false
+ if (size != other.size) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = fileUri?.hashCode() ?: 0
+ result = 31 * result + exists.hashCode()
+ result = 31 * result + (lastModification?.hashCode() ?: 0)
+ result = 31 * result + (size?.hashCode() ?: 0)
+ return result
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): SnapFileDatabaseInfo {
+ return SnapFileDatabaseInfo(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+
+ fun fromFileDatabaseInfo(fileDatabaseInfo: FileDatabaseInfo): SnapFileDatabaseInfo {
+ return SnapFileDatabaseInfo(
+ fileDatabaseInfo.fileUri,
+ fileDatabaseInfo.exists,
+ fileDatabaseInfo.getLastModification(),
+ fileDatabaseInfo.getSize())
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt b/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt
index 682f7889e..bddb8a284 100644
--- a/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt
@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.otp
import com.kunzisoft.keepass.model.OtpModel
+import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.binary.Base64
import org.apache.commons.codec.binary.Hex
@@ -137,7 +138,7 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
@Throws(IllegalArgumentException::class)
fun setHexSecret(secret: String) {
if (secret.isNotEmpty())
- otpModel.secret = Hex.decodeHex(secret)
+ otpModel.secret = Hex.decodeHex(secret.toCharArray())
else
throw IllegalArgumentException()
}
@@ -150,16 +151,16 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
@Throws(IllegalArgumentException::class)
fun setBase32Secret(secret: String) {
- if (isValidBase32(secret))
- otpModel.secret = Base32().decode(replaceBase32Chars(secret).toByteArray())
- else
+ if (isValidBase32(secret)) {
+ otpModel.secret = Base32().decode(replaceBase32Chars(secret))
+ } else
throw IllegalArgumentException()
}
@Throws(IllegalArgumentException::class)
fun setBase64Secret(secret: String) {
if (isValidBase64(secret))
- otpModel.secret = Base64().decode(secret.toByteArray())
+ otpModel.secret = Base64().decode(secret)
else
throw IllegalArgumentException()
}
@@ -208,38 +209,24 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
fun isValidBase32(secret: String): Boolean {
val secretChars = replaceBase32Chars(secret)
- return secretChars.isNotEmpty() && checkBase32Secret(secretChars)
+ return secret.isNotEmpty()
+ && (Pattern.matches("^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?$", secretChars))
}
fun isValidBase64(secret: String): Boolean {
// TODO replace base 64 chars
- return secret.isNotEmpty() && checkBase64Secret(secret)
- }
-
- fun removeLineChars(parameter: String): String {
- return parameter.replace("[\\r|\\n|\\t|\\u00A0]+".toRegex(), "")
- }
-
- fun removeSpaceChars(parameter: String): String {
- return parameter.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
+ return secret.isNotEmpty()
+ && (Pattern.matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", secret))
}
fun replaceBase32Chars(parameter: String): String {
- // Add 'A' at end if not Base32 length
- var parameterNewSize = removeSpaceChars(parameter.toUpperCase(Locale.ENGLISH))
+ // Add padding '=' at end if not Base32 length
+ var parameterNewSize = parameter.toUpperCase(Locale.ENGLISH).removeSpaceChars()
while (parameterNewSize.length % 8 != 0) {
- parameterNewSize += 'A'
+ parameterNewSize += '='
}
return parameterNewSize
}
-
- fun checkBase32Secret(secret: String): Boolean {
- return (Pattern.matches("^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?$", secret))
- }
-
- fun checkBase64Secret(secret: String): Boolean {
- return (Pattern.matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", secret))
- }
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt b/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt
index 38106feca..9e382b39a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt
@@ -24,9 +24,9 @@ import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.Field
-import com.kunzisoft.keepass.otp.OtpElement.Companion.removeLineChars
-import com.kunzisoft.keepass.otp.OtpElement.Companion.removeSpaceChars
import com.kunzisoft.keepass.otp.TokenCalculator.*
+import com.kunzisoft.keepass.utils.StringUtil.removeLineChars
+import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import java.util.*
import java.util.regex.Pattern
@@ -57,13 +57,25 @@ object OtpEntryFields {
private const val DIGITS_KEY = "size"
private const val STEP_KEY = "step"
- // HmacOtp KeePass2 values (https://keepass.info/help/base/placeholders.html#hmacotp)
+ // HmacOtp KeePass2 values (https://keepass.info/help/base/placeholders.html#otp)
private const val HMACOTP_SECRET_FIELD = "HmacOtp-Secret"
private const val HMACOTP_SECRET_HEX_FIELD = "HmacOtp-Secret-Hex"
private const val HMACOTP_SECRET_BASE32_FIELD = "HmacOtp-Secret-Base32"
private const val HMACOTP_SECRET_BASE64_FIELD = "HmacOtp-Secret-Base64"
private const val HMACOTP_SECRET_COUNTER_FIELD = "HmacOtp-Counter"
+ // TimeOtp KeePass2 values
+ private const val TIMEOTP_SECRET_FIELD = "TimeOtp-Secret"
+ private const val TIMEOTP_SECRET_HEX_FIELD = "TimeOtp-Secret-Hex"
+ private const val TIMEOTP_SECRET_BASE32_FIELD = "TimeOtp-Secret-Base32"
+ private const val TIMEOTP_SECRET_BASE64_FIELD = "TimeOtp-Secret-Base64"
+ private const val TIMEOTP_LENGTH_FIELD = "TimeOtp-Length"
+ private const val TIMEOTP_PERIOD_FIELD = "TimeOtp-Period"
+ private const val TIMEOTP_ALGORITHM_FIELD = "TimeOtp-Algorithm"
+ private const val TIMEOTP_ALGORITHM_SHA1_VALUE = "HMAC-SHA-1"
+ private const val TIMEOTP_ALGORITHM_SHA256_VALUE = "HMAC-SHA-256"
+ private const val TIMEOTP_ALGORITHM_SHA512_VALUE = "HMAC-SHA-512"
+
// Custom fields (maybe from plugin)
private const val TOTP_SEED_FIELD = "TOTP Seed"
private const val TOTP_SETTING_FIELD = "TOTP Settings"
@@ -85,14 +97,17 @@ object OtpEntryFields {
// OTP (HOTP/TOTP) from URL and field from KeePassXC
if (parseOTPUri(getField, otpElement))
return otpElement
+ // TOTP from KeePass 2.47
+ if (parseTOTPFromOfficialField(getField, otpElement))
+ return otpElement
// TOTP from key values (maybe plugin or old KeePassXC)
if (parseTOTPKeyValues(getField, otpElement))
return otpElement
// TOTP from custom field
- if (parseTOTPFromField(getField, otpElement))
+ if (parseTOTPFromPluginField(getField, otpElement))
return otpElement
// HOTP fields from KeePass 2
- if (parseHOTPFromField(getField, otpElement))
+ if (parseHOTPFromOfficialField(getField, otpElement))
return otpElement
return null
}
@@ -126,7 +141,7 @@ object OtpEntryFields {
private fun parseOTPUri(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
val otpPlainText = getField(OTP_FIELD)
if (otpPlainText != null && otpPlainText.isNotEmpty() && isOTPUri(otpPlainText)) {
- val uri = Uri.parse(removeSpaceChars(otpPlainText))
+ val uri = Uri.parse(otpPlainText.removeSpaceChars())
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) {
Log.e(TAG, "Invalid or missing scheme in uri")
@@ -159,16 +174,16 @@ object OtpEntryFields {
if (nameParam != null && nameParam.isNotEmpty()) {
val userIdArray = nameParam.split(":", "%3A")
if (userIdArray.size > 1) {
- otpElement.issuer = removeLineChars(userIdArray[0])
- otpElement.name = removeLineChars(userIdArray[1])
+ otpElement.issuer = userIdArray[0].removeLineChars()
+ otpElement.name = userIdArray[1].removeLineChars()
} else {
- otpElement.name = removeLineChars(nameParam)
+ otpElement.name = nameParam.removeLineChars()
}
}
val issuerParam = uri.getQueryParameter(ISSUER_URL_PARAM)
if (issuerParam != null && issuerParam.isNotEmpty())
- otpElement.issuer = removeLineChars(issuerParam)
+ otpElement.issuer = issuerParam.removeLineChars()
val secretParam = uri.getQueryParameter(SECRET_URL_PARAM)
if (secretParam != null && secretParam.isNotEmpty()) {
@@ -247,8 +262,9 @@ object OtpEntryFields {
encodeParameter(username)
else
encodeParameter(otpElement.name)
+ val secret = encodeParameter(otpElement.getBase32Secret())
val uriString = StringBuilder("otpauth://$otpAuthority/$issuer%3A$accountName" +
- "?$SECRET_URL_PARAM=${otpElement.getBase32Secret()}" +
+ "?$SECRET_URL_PARAM=${secret}" +
"&$counterOrPeriodLabel=$counterOrPeriodValue" +
"&$DIGITS_URL_PARAM=${otpElement.digits}" +
"&$ISSUER_URL_PARAM=$issuer")
@@ -262,7 +278,40 @@ object OtpEntryFields {
}
private fun encodeParameter(parameter: String): String {
- return Uri.encode(OtpElement.removeLineChars(parameter))
+ return Uri.encode(parameter.removeLineChars())
+ }
+
+ private fun parseTOTPFromOfficialField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
+ val secretField = getField(TIMEOTP_SECRET_FIELD)
+ val secretHexField = getField(TIMEOTP_SECRET_HEX_FIELD)
+ val secretBase32Field = getField(TIMEOTP_SECRET_BASE32_FIELD)
+ val secretBase64Field = getField(TIMEOTP_SECRET_BASE64_FIELD)
+ val lengthField = getField(TIMEOTP_LENGTH_FIELD)
+ val periodField = getField(TIMEOTP_PERIOD_FIELD)
+ val algorithmField = getField(TIMEOTP_ALGORITHM_FIELD)
+ try {
+ when {
+ secretField != null -> otpElement.setUTF8Secret(secretField)
+ secretHexField != null -> otpElement.setHexSecret(secretHexField)
+ secretBase32Field != null -> otpElement.setBase32Secret(secretBase32Field)
+ secretBase64Field != null -> otpElement.setBase64Secret(secretBase64Field)
+ lengthField != null -> otpElement.digits = lengthField.toIntOrNull() ?: OTP_DEFAULT_DIGITS
+ periodField != null -> otpElement.period = periodField.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
+ algorithmField != null -> otpElement.algorithm =
+ when (algorithmField.toUpperCase(Locale.ENGLISH)) {
+ TIMEOTP_ALGORITHM_SHA1_VALUE -> HashAlgorithm.SHA1
+ TIMEOTP_ALGORITHM_SHA256_VALUE -> HashAlgorithm.SHA256
+ TIMEOTP_ALGORITHM_SHA512_VALUE -> HashAlgorithm.SHA512
+ else -> HashAlgorithm.SHA1
+ }
+ else -> return false
+ }
+ } catch (exception: Exception) {
+ return false
+ }
+
+ otpElement.type = OtpType.TOTP
+ return true
}
private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
@@ -290,7 +339,7 @@ object OtpEntryFields {
return false
}
- private fun parseTOTPFromField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
+ private fun parseTOTPFromPluginField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
val seedField = getField(TOTP_SEED_FIELD) ?: return false
try {
otpElement.setBase32Secret(seedField)
@@ -305,9 +354,15 @@ object OtpEntryFields {
return false
}
otpElement.period = matcher.group(1)?.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
- otpElement.tokenType = matcher.group(2)?.let {
- OtpTokenType.getFromString(it)
- } ?: OtpTokenType.RFC6238
+ matcher.group(2)?.let { secondMatcher ->
+ try {
+ otpElement.digits = secondMatcher.toInt()
+ } catch (e: NumberFormatException) {
+ otpElement.digits = OTP_DEFAULT_DIGITS
+ otpElement.tokenType = OtpTokenType.getFromString(secondMatcher)
+ }
+ }
+
}
} catch (exception: Exception) {
return false
@@ -316,7 +371,7 @@ object OtpEntryFields {
return true
}
- private fun parseHOTPFromField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
+ private fun parseHOTPFromOfficialField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
val secretField = getField(HMACOTP_SECRET_FIELD)
val secretHexField = getField(HMACOTP_SECRET_HEX_FIELD)
val secretBase32Field = getField(HMACOTP_SECRET_BASE32_FIELD)
@@ -382,25 +437,43 @@ object OtpEntryFields {
val totpSeedField = Field(TOTP_SEED_FIELD)
val totpSettingField = Field(TOTP_SETTING_FIELD)
val hmacOtpSecretField = Field(HMACOTP_SECRET_FIELD)
- val hmacOtpSecretHewField = Field(HMACOTP_SECRET_HEX_FIELD)
+ val hmacOtpSecretHexField = Field(HMACOTP_SECRET_HEX_FIELD)
val hmacOtpSecretBase32Field = Field(HMACOTP_SECRET_BASE32_FIELD)
val hmacOtpSecretBase64Field = Field(HMACOTP_SECRET_BASE64_FIELD)
val hmacOtpSecretCounterField = Field(HMACOTP_SECRET_COUNTER_FIELD)
+ val timeOtpSecretField = Field(TIMEOTP_SECRET_FIELD)
+ val timeOtpSecretHexField = Field(TIMEOTP_SECRET_HEX_FIELD)
+ val timeOtpSecretBase32Field = Field(TIMEOTP_SECRET_BASE32_FIELD)
+ val timeOtpSecretBase64Field = Field(TIMEOTP_SECRET_BASE64_FIELD)
+ val timeOtpLengthField = Field(TIMEOTP_LENGTH_FIELD)
+ val timeOtpPeriodField = Field(TIMEOTP_PERIOD_FIELD)
+ val timeOtpAlgorithmField = Field(TIMEOTP_ALGORITHM_FIELD)
newCustomFields.remove(otpField)
newCustomFields.remove(totpSeedField)
newCustomFields.remove(totpSettingField)
newCustomFields.remove(hmacOtpSecretField)
- newCustomFields.remove(hmacOtpSecretHewField)
+ newCustomFields.remove(hmacOtpSecretHexField)
newCustomFields.remove(hmacOtpSecretBase32Field)
newCustomFields.remove(hmacOtpSecretBase64Field)
newCustomFields.remove(hmacOtpSecretCounterField)
+ newCustomFields.remove(timeOtpSecretField)
+ newCustomFields.remove(timeOtpSecretHexField)
+ newCustomFields.remove(timeOtpSecretBase32Field)
+ newCustomFields.remove(timeOtpSecretBase64Field)
+ newCustomFields.remove(timeOtpLengthField)
+ newCustomFields.remove(timeOtpPeriodField)
+ newCustomFields.remove(timeOtpAlgorithmField)
// Empty auto generated OTP Token field
if (fieldsToParse.contains(otpField)
|| fieldsToParse.contains(totpSeedField)
|| fieldsToParse.contains(hmacOtpSecretField)
- || fieldsToParse.contains(hmacOtpSecretHewField)
+ || fieldsToParse.contains(hmacOtpSecretHexField)
|| fieldsToParse.contains(hmacOtpSecretBase32Field)
|| fieldsToParse.contains(hmacOtpSecretBase64Field)
+ || fieldsToParse.contains(timeOtpSecretField)
+ || fieldsToParse.contains(timeOtpSecretHexField)
+ || fieldsToParse.contains(timeOtpSecretBase32Field)
+ || fieldsToParse.contains(timeOtpSecretBase64Field)
)
newCustomFields.add(Field(OTP_TOKEN_FIELD))
return newCustomFields
diff --git a/app/src/main/java/com/kunzisoft/keepass/services/AdvancedUnlockNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/AdvancedUnlockNotificationService.kt
new file mode 100644
index 000000000..26c56d01f
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/services/AdvancedUnlockNotificationService.kt
@@ -0,0 +1,143 @@
+package com.kunzisoft.keepass.services
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Binder
+import android.os.IBinder
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
+import com.kunzisoft.keepass.settings.PreferencesUtil
+import com.kunzisoft.keepass.timeout.TimeoutHelper
+import kotlinx.coroutines.*
+
+class AdvancedUnlockNotificationService : NotificationService() {
+
+ private lateinit var mTempCipherDao: ArrayList
+
+ private var mActionTaskBinder = AdvancedUnlockBinder()
+
+ private var notificationTimeoutMilliSecs: Long = 0
+ private var mTimerJob: Job? = null
+
+ inner class AdvancedUnlockBinder: Binder() {
+ fun getCipherDatabase(databaseUri: Uri): CipherDatabaseEntity? {
+ return mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString()}
+ }
+ fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity) {
+ val cipherDatabaseRetrieve = mTempCipherDao.firstOrNull { it.databaseUri == cipherDatabaseEntity.databaseUri }
+ cipherDatabaseRetrieve?.replaceContent(cipherDatabaseEntity)
+ ?: mTempCipherDao.add(cipherDatabaseEntity)
+ }
+ fun deleteByDatabaseUri(databaseUri: Uri) {
+ mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString() }?.let {
+ mTempCipherDao.remove(it)
+ }
+ }
+ fun deleteAll() {
+ mTempCipherDao.clear()
+ }
+ }
+
+ override val notificationId: Int = 593
+
+ override fun retrieveChannelId(): String {
+ return CHANNEL_ADVANCED_UNLOCK_ID
+ }
+
+ override fun retrieveChannelName(): String {
+ return getString(R.string.advanced_unlock)
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ super.onBind(intent)
+ return mActionTaskBinder
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ super.onStartCommand(intent, flags, startId)
+
+ val deleteIntent = Intent(this, AdvancedUnlockNotificationService::class.java).apply {
+ action = ACTION_REMOVE_KEYS
+ }
+ val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
+ val biometricUnlockEnabled = PreferencesUtil.isBiometricUnlockEnable(this)
+ val notificationBuilder = buildNewNotification().apply {
+ 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))
+ }
+ setContentText(getString(R.string.advanced_unlock_tap_delete))
+ setContentIntent(pendingDeleteIntent)
+ // Unfortunately swipe is disabled in lollipop+
+ setDeleteIntent(pendingDeleteIntent)
+ }
+
+ when (intent?.action) {
+ ACTION_TIMEOUT -> {
+ notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
+ // Not necessarily a foreground service
+ if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
+ mTimerJob = CoroutineScope(Dispatchers.Main).launch {
+ val maxPos = 100
+ val posDurationMills = notificationTimeoutMilliSecs / maxPos
+ for (pos in maxPos downTo 0) {
+ notificationBuilder.setProgress(maxPos, pos, false)
+ startForeground(notificationId, notificationBuilder.build())
+ delay(posDurationMills)
+ if (pos <= 0) {
+ stopSelf()
+ }
+ }
+ notificationManager?.cancel(notificationId)
+ mTimerJob = null
+ cancel()
+ }
+ } else {
+ startForeground(notificationId, notificationBuilder.build())
+ }
+ }
+ ACTION_REMOVE_KEYS -> {
+ stopSelf()
+ }
+ else -> {}
+ }
+
+ return START_STICKY
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ mTempCipherDao = ArrayList()
+ }
+
+ override fun onDestroy() {
+ mTempCipherDao.clear()
+ mTimerJob?.cancel()
+ super.onDestroy()
+ }
+
+ companion object {
+ private const val CHANNEL_ADVANCED_UNLOCK_ID = "com.kunzisoft.keepass.notification.channel.unlock"
+
+ private const val ACTION_TIMEOUT = "ACTION_TIMEOUT"
+ private const val ACTION_REMOVE_KEYS = "ACTION_REMOVE_KEYS"
+
+ fun startServiceForTimeout(context: Context) {
+ if (PreferencesUtil.isTempAdvancedUnlockEnable(context)) {
+ context.startService(Intent(context, AdvancedUnlockNotificationService::class.java).apply {
+ action = ACTION_TIMEOUT
+ })
+ }
+ }
+
+ fun stopService(context: Context) {
+ context.stopService(Intent(context, AdvancedUnlockNotificationService::class.java))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/AttachmentFileNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt
similarity index 99%
rename from app/src/main/java/com/kunzisoft/keepass/notifications/AttachmentFileNotificationService.kt
rename to app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt
index 341f78f22..1e5d7039f 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/AttachmentFileNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/AttachmentFileNotificationService.kt
@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see .
*
*/
-package com.kunzisoft.keepass.notifications
+package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.ContentResolver
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationField.kt b/app/src/main/java/com/kunzisoft/keepass/services/ClipboardEntryNotificationField.kt
similarity index 94%
rename from app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationField.kt
rename to app/src/main/java/com/kunzisoft/keepass/services/ClipboardEntryNotificationField.kt
index 2f4b6e2db..2521db4af 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationField.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/ClipboardEntryNotificationField.kt
@@ -17,12 +17,13 @@
* along with KeePassDX. If not, see .
*
*/
-package com.kunzisoft.keepass.notifications
+package com.kunzisoft.keepass.services
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import com.kunzisoft.keepass.model.EntryInfo
+import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import java.util.*
/**
@@ -54,6 +55,7 @@ class ClipboardEntryNotificationField : Parcelable {
NotificationFieldId.UNKNOWN -> ""
NotificationFieldId.USERNAME -> entryInfo?.username ?: ""
NotificationFieldId.PASSWORD -> entryInfo?.password ?: ""
+ NotificationFieldId.OTP -> entryInfo?.getGeneratedFieldValue(OTP_TOKEN_FIELD) ?: ""
NotificationFieldId.FIELD_A,
NotificationFieldId.FIELD_B,
NotificationFieldId.FIELD_C -> entryInfo?.getGeneratedFieldValue(label) ?: ""
@@ -81,7 +83,7 @@ class ClipboardEntryNotificationField : Parcelable {
}
enum class NotificationFieldId {
- UNKNOWN, USERNAME, PASSWORD, FIELD_A, FIELD_B, FIELD_C;
+ UNKNOWN, USERNAME, PASSWORD, OTP, FIELD_A, FIELD_B, FIELD_C;
companion object {
val anonymousFieldId: Array
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/ClipboardEntryNotificationService.kt
similarity index 93%
rename from app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationService.kt
rename to app/src/main/java/com/kunzisoft/keepass/services/ClipboardEntryNotificationService.kt
index 9ffd23992..2930f5a78 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/ClipboardEntryNotificationService.kt
@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see .
*
*/
-package com.kunzisoft.keepass.notifications
+package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.Context
@@ -25,6 +25,7 @@ import android.content.Intent
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.EntryInfo
+import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper.NEVER
@@ -250,6 +251,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
val containsUsernameToCopy = entry.username.isNotEmpty()
val containsPasswordToCopy = entry.password.isNotEmpty()
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(context)
+ val containsOTPToCopy = entry.containsCustomField(OTP_TOKEN_FIELD)
val containsExtraFieldToCopy = entry.customFields.isNotEmpty()
&& (entry.containsCustomFieldsNotProtected()
||
@@ -262,7 +264,10 @@ class ClipboardEntryNotificationService : LockNotificationService() {
// If notifications enabled in settings
// Don't if application timeout
if (PreferencesUtil.isClipboardNotificationsEnable(context)) {
- if (containsUsernameToCopy || containsPasswordToCopy || containsExtraFieldToCopy) {
+ if (containsUsernameToCopy
+ || containsPasswordToCopy
+ || containsOTPToCopy
+ || containsExtraFieldToCopy) {
// username already copied, waiting for user's action before copy password.
intent.action = ACTION_NEW_NOTIFICATION
@@ -282,14 +287,22 @@ class ClipboardEntryNotificationService : LockNotificationService() {
ClipboardEntryNotificationField.NotificationFieldId.PASSWORD,
context.getString(R.string.entry_password)))
}
+ // Add OTP
+ if (containsOTPToCopy) {
+ notificationFields.add(
+ ClipboardEntryNotificationField(
+ ClipboardEntryNotificationField.NotificationFieldId.OTP,
+ OTP_TOKEN_FIELD))
+ }
// Add extra fields
if (containsExtraFieldToCopy) {
try {
var anonymousFieldNumber = 0
entry.customFields.forEach { field ->
//If value is not protected or allowed
- if (!field.protectedValue.isProtected
- || PreferencesUtil.allowCopyPasswordAndProtectedFields(context)) {
+ if ((!field.protectedValue.isProtected
+ || PreferencesUtil.allowCopyPasswordAndProtectedFields(context))
+ && field.name != OTP_TOKEN_FIELD) {
notificationFields.add(
ClipboardEntryNotificationField(
ClipboardEntryNotificationField.NotificationFieldId.anonymousFieldId[anonymousFieldNumber],
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
similarity index 84%
rename from app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt
rename to app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
index bbc0e9bf1..1ee713ef4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
@@ -17,14 +17,13 @@
* along with KeePassDX. If not, see .
*
*/
-package com.kunzisoft.keepass.notifications
+package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
-import android.os.Binder
-import android.os.Bundle
-import android.os.IBinder
+import android.os.*
+import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
@@ -40,6 +39,8 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
+import com.kunzisoft.keepass.model.MainCredential
+import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.timeout.TimeoutHelper
@@ -47,6 +48,7 @@ import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import com.kunzisoft.keepass.utils.LOCK_ACTION
import com.kunzisoft.keepass.utils.closeDatabase
+import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
@@ -65,6 +67,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private var mAllowFinishAction = AtomicBoolean()
private var mActionRunning = false
+ private var mDatabaseInfoListeners = LinkedList()
+
private var mIconId: Int = R.drawable.notification_ic_database_load
private var mTitleId: Int = R.string.database_opened
private var mMessageId: Int? = null
@@ -93,6 +97,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
mAllowFinishAction.set(false)
}
}
+
+ fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
+ mDatabaseInfoListeners.add(databaseInfoListener)
+ }
+
+ fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
+ mDatabaseInfoListeners.remove(databaseInfoListener)
+ }
}
interface ActionTaskListener {
@@ -101,6 +113,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun onStopAction(actionTask: String, result: ActionRunnable.Result)
}
+ interface DatabaseInfoListener {
+ fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
+ newDatabaseInfo: SnapFileDatabaseInfo)
+ }
+
/**
* Force to call [ActionTaskListener.onStartAction] if the action is still running
*/
@@ -112,6 +129,53 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
+ fun checkDatabaseInfo() {
+ try {
+ mDatabase.fileUri?.let {
+ val previousDatabaseInfo = mSnapFileDatabaseInfo
+ val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
+ FileDatabaseInfo(applicationContext, it))
+
+ val oldDatabaseModification = previousDatabaseInfo?.lastModification
+ val newDatabaseModification = lastFileDatabaseInfo.lastModification
+
+ val conditionExists = previousDatabaseInfo != null
+ && previousDatabaseInfo.exists != lastFileDatabaseInfo.exists
+ // To prevent dialog opening too often
+ val conditionLastModification = (oldDatabaseModification != null && newDatabaseModification != null
+ && oldDatabaseModification < newDatabaseModification
+ && mLastLocalSaveTime + 5000 < newDatabaseModification)
+
+ if (conditionExists || conditionLastModification) {
+ // Show the dialog only if it's real new info and not a delay after a save
+ Log.i(TAG, "Database file modified " +
+ "$previousDatabaseInfo != $lastFileDatabaseInfo ")
+ // Call listener to indicate a change in database info
+ if (previousDatabaseInfo != null) {
+ mDatabaseInfoListeners.forEach { listener ->
+ listener.onDatabaseInfoChanged(previousDatabaseInfo, lastFileDatabaseInfo)
+ }
+ }
+ mSnapFileDatabaseInfo = lastFileDatabaseInfo
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to check database info", e)
+ }
+ }
+
+ fun saveDatabaseInfo() {
+ try {
+ mDatabase.fileUri?.let {
+ mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
+ FileDatabaseInfo(applicationContext, it))
+ Log.i(TAG, "Database file saved $mSnapFileDatabaseInfo")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to check database info", e)
+ }
+ }
+
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return mActionTaskBinder
@@ -138,6 +202,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
val actionRunnable: ActionRunnable? = when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
+ ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask()
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
@@ -192,6 +257,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
actionTaskListener.onStopAction(intentAction!!, result)
}
} finally {
+ // Save the database info before performing action
+ if (intentAction == ACTION_DATABASE_LOAD_TASK) {
+ saveDatabaseInfo()
+ }
+ // Save the database info after performing save action
+ if (intentAction == ACTION_DATABASE_SAVE
+ || intent?.getBooleanExtra(SAVE_DATABASE_KEY, false) == true) {
+ mDatabase.fileUri?.let {
+ val newSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
+ FileDatabaseInfo(applicationContext, it))
+ mLastLocalSaveTime = System.currentTimeMillis()
+ mSnapFileDatabaseInfo = newSnapFileDatabaseInfo
+ }
+ }
removeIntentData(intent)
TimeoutHelper.releaseTemporarilyDisableTimeout()
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
@@ -214,7 +293,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
return when (intentAction) {
- ACTION_DATABASE_LOAD_TASK, null -> {
+ ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_RELOAD_TASK,
+ null -> {
START_STICKY
}
else -> {
@@ -248,7 +329,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
else -> {
when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
- ACTION_DATABASE_LOAD_TASK -> R.string.loading_database
+ ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_RELOAD_TASK -> R.string.loading_database
ACTION_DATABASE_SAVE -> R.string.saving_database
else -> {
R.string.command_execution
@@ -258,13 +340,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
mMessageId = when (intentAction) {
- ACTION_DATABASE_LOAD_TASK -> null
+ ACTION_DATABASE_LOAD_TASK,
+ ACTION_DATABASE_RELOAD_TASK -> null
else -> null
}
mWarningId =
if (!saveAction
- || intentAction == ACTION_DATABASE_LOAD_TASK)
+ || intentAction == ACTION_DATABASE_LOAD_TASK
+ || intentAction == ACTION_DATABASE_RELOAD_TASK)
null
else
R.string.do_not_kill_app
@@ -316,10 +400,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
intent?.removeExtra(DATABASE_TASK_WARNING_KEY)
intent?.removeExtra(DATABASE_URI_KEY)
- intent?.removeExtra(MASTER_PASSWORD_CHECKED_KEY)
- intent?.removeExtra(MASTER_PASSWORD_KEY)
- intent?.removeExtra(KEY_FILE_CHECKED_KEY)
- intent?.removeExtra(KEY_FILE_URI_KEY)
+ intent?.removeExtra(MAIN_CREDENTIAL_KEY)
intent?.removeExtra(READ_ONLY_KEY)
intent?.removeExtra(CIPHER_ENTITY_KEY)
intent?.removeExtra(FIX_DUPLICATE_UUID_KEY)
@@ -342,9 +423,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
* Execute action with a coroutine
*/
private suspend fun executeAction(progressTaskUpdater: ProgressTaskUpdater,
- onPreExecute: () -> Unit,
- onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
- onPostExecute: (result: ActionRunnable.Result) -> Unit) {
+ onPreExecute: () -> Unit,
+ onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
+ onPostExecute: (result: ActionRunnable.Result) -> Unit) {
mAllowFinishAction.set(false)
TimeoutHelper.temporarilyDisableTimeout()
@@ -391,13 +472,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private fun buildDatabaseCreateActionTask(intent: Intent): ActionRunnable? {
if (intent.hasExtra(DATABASE_URI_KEY)
- && intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY)
- && intent.hasExtra(MASTER_PASSWORD_KEY)
- && intent.hasExtra(KEY_FILE_CHECKED_KEY)
- && intent.hasExtra(KEY_FILE_URI_KEY)
+ && intent.hasExtra(MAIN_CREDENTIAL_KEY)
) {
val databaseUri: Uri? = intent.getParcelableExtra(DATABASE_URI_KEY)
- val keyFileUri: Uri? = intent.getParcelableExtra(KEY_FILE_URI_KEY)
+ val mainCredential: MainCredential = intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
if (databaseUri == null)
return null
@@ -407,14 +485,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
databaseUri,
getString(R.string.database_default_name),
getString(R.string.database),
- intent.getBooleanExtra(MASTER_PASSWORD_CHECKED_KEY, false),
- intent.getStringExtra(MASTER_PASSWORD_KEY),
- intent.getBooleanExtra(KEY_FILE_CHECKED_KEY, false),
- keyFileUri
+ mainCredential
) { result ->
result.data = Bundle().apply {
putParcelable(DATABASE_URI_KEY, databaseUri)
- putParcelable(KEY_FILE_URI_KEY, keyFileUri)
+ putParcelable(MAIN_CREDENTIAL_KEY, mainCredential)
}
}
} else {
@@ -425,15 +500,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private fun buildDatabaseLoadActionTask(intent: Intent): ActionRunnable? {
if (intent.hasExtra(DATABASE_URI_KEY)
- && intent.hasExtra(MASTER_PASSWORD_KEY)
- && intent.hasExtra(KEY_FILE_URI_KEY)
+ && intent.hasExtra(MAIN_CREDENTIAL_KEY)
&& intent.hasExtra(READ_ONLY_KEY)
&& intent.hasExtra(CIPHER_ENTITY_KEY)
&& intent.hasExtra(FIX_DUPLICATE_UUID_KEY)
) {
val databaseUri: Uri? = intent.getParcelableExtra(DATABASE_URI_KEY)
- val masterPassword: String? = intent.getStringExtra(MASTER_PASSWORD_KEY)
- val keyFileUri: Uri? = intent.getParcelableExtra(KEY_FILE_URI_KEY)
+ val mainCredential: MainCredential = intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
val readOnly: Boolean = intent.getBooleanExtra(READ_ONLY_KEY, true)
val cipherEntity: CipherDatabaseEntity? = intent.getParcelableExtra(CIPHER_ENTITY_KEY)
@@ -444,8 +517,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
this,
mDatabase,
databaseUri,
- masterPassword,
- keyFileUri,
+ mainCredential,
readOnly,
cipherEntity,
intent.getBooleanExtra(FIX_DUPLICATE_UUID_KEY, false),
@@ -454,8 +526,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
// Add each info to reload database after thrown duplicate UUID exception
result.data = Bundle().apply {
putParcelable(DATABASE_URI_KEY, databaseUri)
- putString(MASTER_PASSWORD_KEY, masterPassword)
- putParcelable(KEY_FILE_URI_KEY, keyFileUri)
+ putParcelable(MAIN_CREDENTIAL_KEY, mainCredential)
putBoolean(READ_ONLY_KEY, readOnly)
putParcelable(CIPHER_ENTITY_KEY, cipherEntity)
}
@@ -465,21 +536,26 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
+ private fun buildDatabaseReloadActionTask(): ActionRunnable {
+ return ReloadDatabaseRunnable(
+ this,
+ mDatabase,
+ this
+ ) { result ->
+ // No need to add each info to reload database
+ result.data = Bundle()
+ }
+ }
+
private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(DATABASE_URI_KEY)
- && intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY)
- && intent.hasExtra(MASTER_PASSWORD_KEY)
- && intent.hasExtra(KEY_FILE_CHECKED_KEY)
- && intent.hasExtra(KEY_FILE_URI_KEY)
+ && intent.hasExtra(MAIN_CREDENTIAL_KEY)
) {
val databaseUri: Uri = intent.getParcelableExtra(DATABASE_URI_KEY) ?: return null
AssignPasswordInDatabaseRunnable(this,
mDatabase,
databaseUri,
- intent.getBooleanExtra(MASTER_PASSWORD_CHECKED_KEY, false),
- intent.getStringExtra(MASTER_PASSWORD_KEY),
- intent.getBooleanExtra(KEY_FILE_CHECKED_KEY, false),
- intent.getParcelableExtra(KEY_FILE_URI_KEY)
+ intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
)
} else {
null
@@ -770,6 +846,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK"
+ const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK"
@@ -801,10 +878,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val DATABASE_TASK_WARNING_KEY = "DATABASE_TASK_WARNING_KEY"
const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
- const val MASTER_PASSWORD_CHECKED_KEY = "MASTER_PASSWORD_CHECKED_KEY"
- const val MASTER_PASSWORD_KEY = "MASTER_PASSWORD_KEY"
- const val KEY_FILE_CHECKED_KEY = "KEY_FILE_CHECKED_KEY"
- const val KEY_FILE_URI_KEY = "KEY_FILE_URI_KEY"
+ const val MAIN_CREDENTIAL_KEY = "MAIN_CREDENTIAL_KEY"
const val READ_ONLY_KEY = "READ_ONLY_KEY"
const val CIPHER_ENTITY_KEY = "CIPHER_ENTITY_KEY"
const val FIX_DUPLICATE_UUID_KEY = "FIX_DUPLICATE_UUID_KEY"
@@ -822,6 +896,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time
const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time
+ private var mSnapFileDatabaseInfo: SnapFileDatabaseInfo? = null
+ private var mLastLocalSaveTime: Long = 0
+
fun getListNodesFromBundle(database: Database, bundle: Bundle): List {
val nodesAction = ArrayList()
bundle.getParcelableArrayList>(GROUPS_ID_KEY)?.forEach {
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/KeyboardEntryNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/KeyboardEntryNotificationService.kt
similarity index 99%
rename from app/src/main/java/com/kunzisoft/keepass/notifications/KeyboardEntryNotificationService.kt
rename to app/src/main/java/com/kunzisoft/keepass/services/KeyboardEntryNotificationService.kt
index 1659bb23d..af214e50b 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/KeyboardEntryNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/KeyboardEntryNotificationService.kt
@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see .
*
*/
-package com.kunzisoft.keepass.notifications
+package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.Context
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/LockNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/LockNotificationService.kt
similarity index 97%
rename from app/src/main/java/com/kunzisoft/keepass/notifications/LockNotificationService.kt
rename to app/src/main/java/com/kunzisoft/keepass/services/LockNotificationService.kt
index 2bb4889ec..daf37a124 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/LockNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/LockNotificationService.kt
@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see .
*
*/
-package com.kunzisoft.keepass.notifications
+package com.kunzisoft.keepass.services
import android.content.Intent
import com.kunzisoft.keepass.utils.LockReceiver
diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/NotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/NotificationService.kt
similarity index 98%
rename from app/src/main/java/com/kunzisoft/keepass/notifications/NotificationService.kt
rename to app/src/main/java/com/kunzisoft/keepass/services/NotificationService.kt
index 48b7106c0..e475ad4eb 100644
--- a/app/src/main/java/com/kunzisoft/keepass/notifications/NotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/NotificationService.kt
@@ -1,4 +1,4 @@
-package com.kunzisoft.keepass.notifications
+package com.kunzisoft.keepass.services
import android.app.NotificationChannel
import android.app.NotificationManager
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt
index cba9c231d..f3462752b 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt
@@ -19,10 +19,12 @@
*/
package com.kunzisoft.keepass.settings
+import android.os.Build
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreference
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistAppIdPreferenceDialogFragmentCompat
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistWebDomainPreferenceDialogFragmentCompat
@@ -32,6 +34,11 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
// Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences_autofill, rootKey)
+
+ val autofillInlineSuggestionsPreference: SwitchPreference? = findPreference(getString(R.string.autofill_inline_suggestions_key))
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ autofillInlineSuggestionsPreference?.isVisible = false
+ }
}
override fun onDisplayPreferenceDialog(preference: Preference?) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt
index d03cd3411..b6b0acb7f 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt
@@ -103,6 +103,6 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
}
interface Callback {
- fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen)
+ fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean = false)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt
index d6d14c4f6..b9ebbab67 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt
@@ -41,9 +41,10 @@ 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.services.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.utils.UriUtil
@@ -214,9 +215,10 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key))
val deviceCredentialUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.device_credential_unlock_enable_key))
val autoOpenPromptPreference: SwitchPreference? = findPreference(getString(R.string.biometric_auto_open_prompt_key))
+ 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
@@ -237,6 +239,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
deleteKeysMessage(activity) {
biometricUnlockEnablePreference.isChecked = false
autoOpenPromptPreference?.isEnabled = deviceCredentialChecked
+ tempAdvancedUnlockPreference?.isEnabled = deviceCredentialChecked
}
} else {
if (deviceCredentialChecked) {
@@ -247,6 +250,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
} else {
autoOpenPromptPreference?.isEnabled = true
+ tempAdvancedUnlockPreference?.isEnabled = true
}
}
true
@@ -254,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
}
@@ -275,6 +282,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
deleteKeysMessage(activity) {
deviceCredentialUnlockEnablePreference.isChecked = false
autoOpenPromptPreference?.isEnabled = biometricChecked
+ tempAdvancedUnlockPreference?.isEnabled = biometricChecked
}
} else {
if (biometricChecked) {
@@ -285,6 +293,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
} else {
autoOpenPromptPreference?.isEnabled = true
+ tempAdvancedUnlockPreference?.isEnabled = true
}
}
true
@@ -294,6 +303,16 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
autoOpenPromptPreference?.isEnabled = biometricUnlockEnablePreference?.isChecked == true
|| deviceCredentialUnlockEnablePreference?.isChecked == true
+ tempAdvancedUnlockPreference?.isEnabled = biometricUnlockEnablePreference?.isChecked == true
+ || deviceCredentialUnlockEnablePreference?.isChecked == true
+
+ tempAdvancedUnlockPreference?.setOnPreferenceClickListener {
+ tempAdvancedUnlockPreference.isChecked = !tempAdvancedUnlockPreference.isChecked
+ deleteKeysMessage(activity) {
+ tempAdvancedUnlockPreference.isChecked = !tempAdvancedUnlockPreference.isChecked
+ }
+ true
+ }
val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key))
if (biometricUnlockSupported || deviceCredentialUnlockSupported) {
@@ -321,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),
@@ -334,11 +353,12 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
showException(e)
}
- override fun onBiometricException(e: Exception) {
+ override fun onGenericException(e: Exception) {
showException(e)
}
})
}
+ AdvancedUnlockNotificationService.stopService(activity.applicationContext)
CipherDatabaseAction.getInstance(activity.applicationContext).deleteAll()
}
.setNegativeButton(resources.getString(android.R.string.cancel)
@@ -366,7 +386,13 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
if (styleEnabled) {
Stylish.assignStyle(styleIdString)
- activity.recreate()
+ // Relaunch the current activity to redraw theme
+ (activity as? SettingsActivity?)?.apply {
+ keepCurrentScreen()
+ startActivity(intent)
+ finish()
+ activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
+ }
}
styleEnabled
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
index de0bbadda..fe9a26dc9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
@@ -35,7 +35,7 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
-import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -58,7 +58,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
private var mEncryptionAlgorithmPref: DialogListExplanationPreference? = null
private var mKeyDerivationPref: DialogListExplanationPreference? = null
private var mRoundPref: InputKdfNumberPreference? = null
- private var mMemoryPref: InputKdfNumberPreference? = null
+ private var mMemoryPref: InputKdfSizePreference? = null
private var mParallelismPref: InputKdfNumberPreference? = null
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
@@ -231,7 +231,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
}
// Memory Usage
- mMemoryPref = findPreference(getString(R.string.memory_usage_key))?.apply {
+ mMemoryPref = findPreference(getString(R.string.memory_usage_key))?.apply {
summary = mDatabase.memoryUsage.toString()
}
@@ -552,6 +552,13 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
true
}
+ R.id.menu_reload_database -> {
+ settingActivity?.apply {
+ keepCurrentScreen()
+ mProgressDatabaseTaskProvider?.startDatabaseReload(false)
+ }
+ return true
+ }
else -> {
// Check the time lock before launching settings
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt
index 2cc7215f7..857921440 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt
@@ -35,9 +35,13 @@ abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY
}
+ fun getScreen(): Screen {
+ return Screen.values()[requireArguments().getInt(TAG_KEY)]
+ }
+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
onCreateScreenPreference(
- Screen.values()[requireArguments().getInt(TAG_KEY)],
+ getScreen(),
savedInstanceState,
rootKey)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt
index f6d9bf2be..f2c166d9e 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt
@@ -19,12 +19,14 @@
*/
package com.kunzisoft.keepass.settings
+import android.app.backup.BackupManager
import android.content.Context
import android.content.res.Resources
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.*
@@ -43,6 +45,7 @@ object PreferencesUtil {
}
apply()
}
+ BackupManager(context).dataChanged()
}
fun getDefaultDatabasePath(context: Context): String? {
@@ -201,6 +204,13 @@ object PreferencesUtil {
?: TimeoutHelper.DEFAULT_TIMEOUT
}
+ fun getAdvancedUnlockTimeout(context: Context): Long {
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+ return prefs.getString(context.getString(R.string.temp_advanced_unlock_timeout_key),
+ context.getString(R.string.temp_advanced_unlock_timeout_default))?.toLong()
+ ?: TimeoutHelper.DEFAULT_TIMEOUT
+ }
+
fun isLockDatabaseWhenScreenShutOffEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.lock_database_screen_off_key),
@@ -231,8 +241,29 @@ 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 {
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+ return prefs.getBoolean(context.getString(R.string.temp_advanced_unlock_enable_key),
+ context.resources.getBoolean(R.bool.temp_advanced_unlock_enable_default))
}
fun isAdvancedUnlockPromptAutoOpenEnable(context: Context): Boolean {
@@ -241,12 +272,6 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.biometric_auto_open_prompt_default))
}
- fun isDeviceCredentialUnlockEnable(context: Context): Boolean {
- val prefs = PreferenceManager.getDefaultSharedPreferences(context)
- return prefs.getBoolean(context.getString(R.string.device_credential_unlock_enable_key),
- context.resources.getBoolean(R.bool.device_credential_unlock_enable_default))
- }
-
fun getListSort(context: Context): SortNodeEnum {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.getString(context.getString(R.string.sort_node_key),
@@ -411,13 +436,18 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.autofill_close_database_default))
}
-
fun isAutofillAutoSearchEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
context.resources.getBoolean(R.bool.autofill_auto_search_default))
}
+ fun isAutofillInlineSuggestionsEnable(context: Context): Boolean {
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+ return prefs.getBoolean(context.getString(R.string.autofill_inline_suggestions_key),
+ context.resources.getBoolean(R.bool.autofill_inline_suggestions_default))
+ }
+
fun isAutofillSaveSearchInfoEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_save_search_info_key),
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
index 2188498a2..4760e86c2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
+ * Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.settings
import android.app.Activity
import android.app.backup.BackupManager
-import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -35,9 +34,12 @@ 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.model.MainCredential
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.timeout.TimeoutHelper
-import com.kunzisoft.keepass.view.showActionError
+import com.kunzisoft.keepass.view.showActionErrorIfNeeded
open class SettingsActivity
: LockingActivity(),
@@ -81,7 +83,7 @@ open class SettingsActivity
}
// Focus view to reinitialize timeout
- resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
+ coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
@@ -94,12 +96,35 @@ open class SettingsActivity
backupManager = BackupManager(this)
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
- // Call result in fragment
- (supportFragmentManager
- .findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
- ?.onProgressDialogThreadResult(actionTask, result)
+ when (actionTask) {
+ DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
+ // Reload the current activity
+ if (result.isSuccess) {
+ startActivity(intent)
+ finish()
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
+ } else {
+ this.showActionErrorIfNeeded(result)
+ finish()
+ }
+ }
+ else -> {
+ // Call result in fragment
+ (supportFragmentManager
+ .findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
+ ?.onProgressDialogThreadResult(actionTask, result)
+ }
+ }
+ coordinatorLayout?.showActionErrorIfNeeded(result)
+ }
- coordinatorLayout?.showActionError(result)
+ // To reload the current screen
+ if (intent.extras?.containsKey(FRAGMENT_ARG) == true) {
+ intent.extras?.getString(FRAGMENT_ARG)?.let { fragmentScreenName ->
+ onNestedPreferenceSelected(NestedSettingsFragment.Screen.valueOf(fragmentScreenName), true)
+ }
+ // Eat state
+ intent.removeExtra(FRAGMENT_ARG)
}
}
@@ -117,52 +142,33 @@ open class SettingsActivity
}
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
- masterPasswordChecked: Boolean,
- masterPassword: String?,
- keyFileChecked: Boolean,
- keyFile: Uri?) {
+ mainCredential: MainCredential) {
databaseUri?.let {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
- masterPasswordChecked,
- masterPassword,
- keyFileChecked,
- keyFile
+ mainCredential
)
}
}
- override fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean,
- masterPassword: String?,
- keyFileChecked: Boolean,
- keyFile: Uri?) {
+ override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
Database.getInstance().let { database ->
database.fileUri?.let { databaseUri ->
// Show the progress dialog now or after dialog confirmation
- if (database.validatePasswordEncoding(masterPassword, keyFileChecked)) {
+ if (database.validatePasswordEncoding(mainCredential)) {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
- masterPasswordChecked,
- masterPassword,
- keyFileChecked,
- keyFile
+ mainCredential
)
} else {
- PasswordEncodingDialogFragment.getInstance(databaseUri,
- masterPasswordChecked,
- masterPassword,
- keyFileChecked,
- keyFile
- ).show(supportFragmentManager, "passwordEncodingTag")
+ PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential)
+ .show(supportFragmentManager, "passwordEncodingTag")
}
}
}
}
- override fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean,
- masterPassword: String?,
- keyFileChecked: Boolean,
- keyFile: Uri?) {}
+ override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {}
private fun hideOrShowLockButton(key: NestedSettingsFragment.Screen) {
if (PreferencesUtil.showLockDatabaseButton(this)) {
@@ -192,25 +198,41 @@ open class SettingsActivity
hideOrShowLockButton(NestedSettingsFragment.Screen.APPLICATION)
}
- private fun replaceFragment(key: NestedSettingsFragment.Screen) {
- supportFragmentManager.beginTransaction()
- .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
+ private fun replaceFragment(key: NestedSettingsFragment.Screen, reload: Boolean) {
+ supportFragmentManager.beginTransaction().apply {
+ if (reload) {
+ setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out,
R.anim.slide_in_left, R.anim.slide_out_right)
- .replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
- .addToBackStack(TAG_NESTED)
- .commit()
+ } else {
+ setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
+ R.anim.slide_in_left, R.anim.slide_out_right)
+ }
+ replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
+ addToBackStack(TAG_NESTED)
+ commit()
+ }
toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key)
hideOrShowLockButton(key)
}
- override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen) {
+ /**
+ * To keep the current screen when activity is reloaded
+ */
+ fun keepCurrentScreen() {
+ (supportFragmentManager.findFragmentByTag(TAG_NESTED) as? NestedSettingsFragment?)
+ ?.getScreen()?.let { fragmentKey ->
+ intent.putExtra(FRAGMENT_ARG, fragmentKey.name)
+ }
+ }
+
+ override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
if (mTimeoutEnable)
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
- replaceFragment(key)
+ replaceFragment(key, reload)
}
else
- replaceFragment(key)
+ replaceFragment(key, reload)
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -225,6 +247,7 @@ open class SettingsActivity
private const val SHOW_LOCK = "SHOW_LOCK"
private const val TITLE_KEY = "TITLE_KEY"
private const val TAG_NESTED = "TAG_NESTED"
+ private const val FRAGMENT_ARG = "FRAGMENT_ARG"
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
val intent = Intent(activity, SettingsActivity::class.java)
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
index 9a8376f02..766f3d634 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
@@ -25,7 +25,7 @@ import androidx.preference.DialogPreference
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
-class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
+open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
new file mode 100644
index 000000000..9fe61e3a3
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.settings.preference
+
+import android.content.Context
+import android.util.AttributeSet
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.utils.DataByte
+
+class InputKdfSizePreference @JvmOverloads constructor(context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleRes: Int = defStyleAttr)
+ : InputKdfNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
+
+ override fun setSummary(summary: CharSequence) {
+ if (summary == UNKNOWN_VALUE_STRING) {
+ super.setSummary("")
+ } else {
+ var summaryString = summary
+ try {
+ val memorySize = summary.toString().toLong()
+ summaryString = if (memorySize > 0) {
+ // To convert bytes to mebibytes
+ DataByte(memorySize, DataByte.ByteFormat.BYTE)
+ .toBetterByteFormat().toString(context)
+ } else {
+ memorySize.toString()
+ }
+ } catch (e: Exception) {
+ } finally {
+ super.setSummary(summaryString)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
index be27d4d65..e0fe8da97 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
@@ -36,7 +36,7 @@ open class InputNumberPreference @JvmOverloads constructor(context: Context,
override fun setSummary(summary: CharSequence) {
if (summary == INFINITE_VALUE_STRING) {
- super.setSummary("")
+ super.setSummary("∞")
} else {
super.setSummary(summary)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
new file mode 100644
index 000000000..90b1e42d1
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.settings.preference
+
+import android.content.Context
+import android.util.AttributeSet
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.utils.DataByte
+
+open class InputSizePreference @JvmOverloads constructor(context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleRes: Int = defStyleAttr)
+ : InputNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
+
+ override fun setSummary(summary: CharSequence) {
+ var summaryString = summary
+ try {
+ val memorySize = summary.toString().toLong()
+ summaryString = if (memorySize >= 0) {
+ // To convert bytes to mebibytes
+ DataByte(memorySize, DataByte.ByteFormat.BYTE)
+ .toBetterByteFormat().toString(context)
+ } else {
+ memorySize.toString()
+ }
+ } catch (e: Exception) {
+ } finally {
+ super.setSummary(summaryString)
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt
index 7932e3d3d..11751d2fb 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt
@@ -33,6 +33,7 @@ import com.kunzisoft.keepass.R
abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
private var inputTextView: EditText? = null
+ private var textUnitView: TextView? = null
private var textExplanationView: TextView? = null
private var switchElementView: CompoundButton? = null
@@ -47,6 +48,14 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
}
}
+ fun setInoutText(@StringRes inputTextId: Int) {
+ inputText = getString(inputTextId)
+ }
+
+ fun showInputText(show: Boolean) {
+ inputTextView?.visibility = if (show) View.VISIBLE else View.GONE
+ }
+
fun setInputTextError(error: CharSequence) {
this.inputTextView?.error = error
}
@@ -55,6 +64,24 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
this.mOnInputTextEditorActionListener = onEditorActionListener
}
+ var unitText: String?
+ get() = textUnitView?.text?.toString() ?: ""
+ set(unitText) {
+ textUnitView?.apply {
+ if (unitText != null && unitText.isNotEmpty()) {
+ text = unitText
+ visibility = View.VISIBLE
+ } else {
+ text = ""
+ visibility = View.GONE
+ }
+ }
+ }
+
+ fun setUnitText(@StringRes unitTextId: Int) {
+ unitText = getString(unitTextId)
+ }
+
var explanationText: String?
get() = textExplanationView?.text?.toString() ?: ""
set(explanationText) {
@@ -69,6 +96,10 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
}
}
+ fun setExplanationText(@StringRes explanationTextId: Int) {
+ explanationText = getString(explanationTextId)
+ }
+
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
@@ -93,6 +124,8 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
}
}
}
+ textUnitView = view.findViewById(R.id.input_text_unit)
+ textUnitView?.visibility = View.GONE
textExplanationView = view.findViewById(R.id.explanation_text)
textExplanationView?.visibility = View.GONE
switchElementView = view.findViewById(R.id.switch_element)
@@ -113,18 +146,6 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
return false
}
- fun setInoutText(@StringRes inputTextId: Int) {
- inputText = getString(inputTextId)
- }
-
- fun showInputText(show: Boolean) {
- inputTextView?.visibility = if (show) View.VISIBLE else View.GONE
- }
-
- fun setExplanationText(@StringRes explanationTextId: Int) {
- explanationText = getString(explanationTextId)
- }
-
fun setSwitchAction(onCheckedChange: ((isChecked: Boolean)-> Unit)?, defaultChecked: Boolean) {
switchElementView?.visibility = if (onCheckedChange == null) View.GONE else View.VISIBLE
switchElementView?.isChecked = defaultChecked
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt
index 61a0cb9d1..4a15d806e 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt
@@ -22,50 +22,76 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.utils.DataByte
class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
+ private var dataByte = DataByte(2L, DataByte.ByteFormat.MEBIBYTE)
+
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.max_history_size_summary)
database?.historyMaxSize?.let { maxItemsDatabase ->
- inputText = maxItemsDatabase.toString()
+ dataByte = DataByte(maxItemsDatabase, DataByte.ByteFormat.BYTE)
+ .toBetterByteFormat()
+ inputText = dataByte.number.toString()
+ if (dataByte.number >= 0) {
+ setUnitText(dataByte.format.stringId)
+ } else {
+ unitText = null
+ }
+
setSwitchAction({ isChecked ->
- inputText = if (!isChecked) {
- INFINITE_MAX_HISTORY_SIZE.toString()
- } else
- DEFAULT_MAX_HISTORY_SIZE.toString()
+ if (!isChecked) {
+ dataByte = INFINITE_MAX_HISTORY_SIZE_DATA_BYTE
+ inputText = INFINITE_MAX_HISTORY_SIZE.toString()
+ unitText = null
+ } else {
+ dataByte = DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE
+ inputText = dataByte.number.toString()
+ setUnitText(dataByte.format.stringId)
+ }
showInputText(isChecked)
}, maxItemsDatabase > INFINITE_MAX_HISTORY_SIZE)
+
}
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
database?.let { database ->
- var maxHistorySize: Long = try {
+ val maxHistorySize: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
- DEFAULT_MAX_HISTORY_SIZE
+ DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE.toBytes()
}
- if (maxHistorySize < INFINITE_MAX_HISTORY_SIZE) {
- maxHistorySize = INFINITE_MAX_HISTORY_SIZE
+ val numberOfBytes = if (maxHistorySize >= 0) {
+ val dataByteConversion = DataByte(maxHistorySize, dataByte.format)
+ var bytes = dataByteConversion.toBytes()
+ if (bytes > Long.MAX_VALUE) {
+ bytes = Long.MAX_VALUE
+ }
+ bytes
+ } else {
+ INFINITE_MAX_HISTORY_SIZE
}
val oldMaxHistorySize = database.historyMaxSize
- database.historyMaxSize = maxHistorySize
+ database.historyMaxSize = numberOfBytes
- mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, maxHistorySize, mDatabaseAutoSaveEnable)
+ mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, numberOfBytes, mDatabaseAutoSaveEnable)
}
}
}
companion object {
- const val DEFAULT_MAX_HISTORY_SIZE = 134217728L
const val INFINITE_MAX_HISTORY_SIZE = -1L
+ private val INFINITE_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(INFINITE_MAX_HISTORY_SIZE, DataByte.ByteFormat.MEBIBYTE)
+ private val DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(6L, DataByte.ByteFormat.MEBIBYTE)
+
fun newInstance(key: String): MaxHistorySizePreferenceDialogFragmentCompat {
val fragment = MaxHistorySizePreferenceDialogFragmentCompat()
val bundle = Bundle(1)
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt
index 11cd7dc8e..184989436 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt
@@ -22,33 +22,46 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.utils.DataByte
class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
+ private var dataByte = DataByte(MIN_MEMORY_USAGE, DataByte.ByteFormat.BYTE)
+
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.memory_usage_explanation)
- inputText = database?.memoryUsage?.toString()?: MIN_MEMORY_USAGE.toString()
+
+ val memoryBytes = database?.memoryUsage ?: MIN_MEMORY_USAGE
+ dataByte = DataByte(memoryBytes, DataByte.ByteFormat.BYTE)
+ .toBetterByteFormat()
+ inputText = dataByte.number.toString()
+ setUnitText(dataByte.format.stringId)
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
database?.let { database ->
- var memoryUsage: Long = try {
+ var newMemoryUsage: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
MIN_MEMORY_USAGE
}
- if (memoryUsage < MIN_MEMORY_USAGE) {
- memoryUsage = MIN_MEMORY_USAGE
+ if (newMemoryUsage < MIN_MEMORY_USAGE) {
+ newMemoryUsage = MIN_MEMORY_USAGE
+ }
+ // To transform in bytes
+ dataByte.number = newMemoryUsage
+ var numberOfBytes = dataByte.toBytes()
+ if (numberOfBytes > Long.MAX_VALUE) {
+ numberOfBytes = Long.MAX_VALUE
}
- // TODO Max Memory
val oldMemoryUsage = database.memoryUsage
- database.memoryUsage = memoryUsage
+ database.memoryUsage = numberOfBytes
- mProgressDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(oldMemoryUsage, memoryUsage, mDatabaseAutoSaveEnable)
+ mProgressDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(oldMemoryUsage, numberOfBytes, mDatabaseAutoSaveEnable)
}
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt b/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt
index b071c33d6..084b12bd2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/tasks/AttachmentFileBinderManager.kt
@@ -31,10 +31,10 @@ import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
-import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
-import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
-import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
-import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE
+import com.kunzisoft.keepass.services.AttachmentFileNotificationService
+import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
+import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
+import com.kunzisoft.keepass.services.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE
class AttachmentFileBinderManager(private val activity: FragmentActivity) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt b/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt
index 59c97ac7c..c12fb0838 100644
--- a/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/utils/BroadcastAction.kt
@@ -32,8 +32,8 @@ import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.magikeyboard.MagikIME
-import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
-import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
+import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
+import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
cancelAll()
}
// Clear data
- Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
+ Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/DataByte.kt b/app/src/main/java/com/kunzisoft/keepass/utils/DataByte.kt
new file mode 100644
index 000000000..4a8e53d6c
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/utils/DataByte.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.utils
+
+import android.content.Context
+import androidx.annotation.StringRes
+import com.kunzisoft.keepass.R
+
+class DataByte(var number: Long, var format: ByteFormat) {
+
+ fun toBetterByteFormat(): DataByte {
+ return when (this.format) {
+ ByteFormat.BYTE -> {
+ when {
+ //this.number % GIBIBYTES == 0L -> {
+ // DataByte((this.number / GIBIBYTES), ByteFormat.GIBIBYTE)
+ //}
+ this.number % MEBIBYTES == 0L -> {
+ DataByte((this.number / MEBIBYTES), ByteFormat.MEBIBYTE)
+ }
+ this.number % KIBIBYTES == 0L -> {
+ DataByte((this.number / KIBIBYTES), ByteFormat.KIBIBYTE)
+ }
+ else -> {
+ DataByte(this.number, ByteFormat.BYTE)
+ }
+ }
+ }
+ else -> {
+ DataByte(toBytes(), ByteFormat.BYTE).toBetterByteFormat()
+ }
+ }
+ }
+
+ /**
+ * Number of bytes in current DataByte
+ */
+ fun toBytes(): Long {
+ return when (this.format) {
+ ByteFormat.BYTE -> this.number
+ ByteFormat.KIBIBYTE -> this.number * KIBIBYTES
+ ByteFormat.MEBIBYTE -> this.number * MEBIBYTES
+ //ByteFormat.GIBIBYTE -> this.number * GIBIBYTES
+ }
+ }
+
+ override fun toString(): String {
+ return "$number ${format.name}"
+ }
+
+ fun toString(context: Context): String {
+ return "$number ${context.getString(format.stringId)}"
+ }
+
+ enum class ByteFormat(@StringRes var stringId: Int) {
+ BYTE(R.string.unit_byte),
+ KIBIBYTE(R.string.unit_kibibyte),
+ MEBIBYTE(R.string.unit_mebibyte)
+ //GIBIBYTE(R.string.unit_gibibyte)
+ }
+
+ companion object {
+ const val KIBIBYTES = 1024L
+ const val MEBIBYTES = 1048576L
+ const val GIBIBYTES = 1073741824L
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt
index 63d5cb7dc..38a311fc2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt
@@ -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
}
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/StringUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/StringUtil.kt
new file mode 100644
index 000000000..90aeb12ac
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/utils/StringUtil.kt
@@ -0,0 +1,14 @@
+package com.kunzisoft.keepass.utils
+
+object StringUtil {
+
+ fun String.removeLineChars(): String {
+ return this.replace("[\\r|\\n|\\t|\\u00A0]+".toRegex(), "")
+ }
+
+ fun String.removeSpaceChars(): String {
+ return this.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
+ }
+
+ fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt
index 974eab131..e54399b2a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt
@@ -48,8 +48,8 @@ import java.util.*
class EntryContentsView @JvmOverloads constructor(context: Context,
- var attrs: AttributeSet? = null,
- var defStyle: Int = 0)
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle) {
private var fontInVisibility: Boolean = false
@@ -67,7 +67,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private val creationDateView: TextView
private val modificationDateView: TextView
- private val lastAccessDateView: TextView
private val expiresImageView: ImageView
private val expiresDateView: TextView
@@ -117,7 +116,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
creationDateView = findViewById(R.id.entry_created)
modificationDateView = findViewById(R.id.entry_modified)
- lastAccessDateView = findViewById(R.id.entry_accessed)
expiresImageView = findViewById(R.id.entry_expires_image)
expiresDateView = findViewById(R.id.entry_expires_date)
@@ -258,20 +256,13 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
modificationDateView.text = date.getDateTimeString(resources)
}
- fun assignLastAccessDate(date: DateInstant) {
- lastAccessDateView.text = date.getDateTimeString(resources)
- }
-
- fun setExpires(isExpires: Boolean) {
+ fun setExpires(isExpires: Boolean, expiryTime: DateInstant) {
expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE
- }
-
- fun assignExpiresDate(date: DateInstant) {
- assignExpiresDate(date.getDateTimeString(resources))
- }
-
- fun assignExpiresDate(constString: String) {
- expiresDateView.text = constString
+ expiresDateView.text = if (isExpires) {
+ expiryTime.getDateTimeString(resources)
+ } else {
+ resources.getString(R.string.never)
+ }
}
fun assignUUID(uuid: UUID) {
@@ -279,7 +270,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
uuidReferenceView.text = UuidUtil.toHexString(uuid)
}
-
fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) {
passwordFieldView.hiddenProtectedValue = hiddenProtectedValue
// Hidden style for custom fields
@@ -306,7 +296,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
allowCopy: Boolean,
onCopyButtonClickListener: OnClickListener?) {
- val entryCustomField: EntryField? = EntryField(context, attrs, defStyle)
+ val entryCustomField: EntryField? = EntryField(context)
entryCustomField?.apply {
setLabel(title)
setValue(value.toString(), value.isProtected)
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt b/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt
index cee87171a..1dc1f370d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/KeyFileSelectionView.kt
@@ -4,7 +4,6 @@ import android.content.Context
import android.net.Uri
import android.util.AttributeSet
import android.view.LayoutInflater
-import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.documentfile.provider.DocumentFile
@@ -20,7 +19,6 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
private val keyFileNameInputLayout: TextInputLayout
private val keyFileNameView: TextView
- private val keyFileOpenView: ImageView
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
@@ -28,7 +26,6 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
keyFileNameInputLayout = findViewById(R.id.input_entry_keyfile)
keyFileNameView = findViewById(R.id.keyfile_name)
- keyFileOpenView = findViewById(R.id.keyfile_open_button)
}
override fun setOnClickListener(l: OnClickListener?) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt
index 2e25e9192..cc86a0296 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt
@@ -31,7 +31,7 @@ import com.kunzisoft.keepass.R
class ToolbarAction @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyle: Int = androidx.appcompat.R.attr.toolbarStyle)
+ defStyle: Int = R.attr.actionToolbarAppearance)
: Toolbar(context, attrs, defStyle) {
private var mActionModeCallback: ActionMode.Callback? = null
@@ -39,7 +39,7 @@ class ToolbarAction @JvmOverloads constructor(context: Context,
private var isOpen = false
init {
- visibility = View.GONE
+ setNavigationIcon(R.drawable.ic_close_white_24dp)
}
fun startSupportActionMode(actionModeCallback: ActionMode.Callback): ActionMode {
@@ -55,8 +55,6 @@ class ToolbarAction @JvmOverloads constructor(context: Context,
actionMode.finish()
}
- setNavigationIcon(R.drawable.ic_close_white_24dp)
-
open()
return actionMode
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
index 720b6cfd7..df734a796 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.view
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
+import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
@@ -35,6 +36,7 @@ import android.text.style.ClickableSpan
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.TextView
+import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.updatePadding
import com.google.android.material.snackbar.Snackbar
@@ -166,7 +168,17 @@ fun View.updateLockPaddingLeft() {
))
}
-fun CoordinatorLayout.showActionError(result: ActionRunnable.Result) {
+fun Context.showActionErrorIfNeeded(result: ActionRunnable.Result) {
+ if (!result.isSuccess) {
+ result.exception?.errorId?.let { errorId ->
+ Toast.makeText(this, errorId, Toast.LENGTH_LONG).show()
+ } ?: result.message?.let { message ->
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+ }
+ }
+}
+
+fun CoordinatorLayout.showActionErrorIfNeeded(result: ActionRunnable.Result) {
if (!result.isSuccess) {
result.exception?.errorId?.let { errorId ->
Snackbar.make(this, errorId, Snackbar.LENGTH_LONG).asError().show()
diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFileViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFileViewModel.kt
index 449f5da10..ffc8f8d8d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFileViewModel.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFileViewModel.kt
@@ -4,8 +4,12 @@ import android.app.Application
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
+import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
+import com.kunzisoft.keepass.app.database.IOActionTask
import com.kunzisoft.keepass.model.DatabaseFile
+import com.kunzisoft.keepass.settings.PreferencesUtil
+import com.kunzisoft.keepass.utils.UriUtil
class DatabaseFileViewModel(application: Application) : AndroidViewModel(application) {
@@ -15,6 +19,33 @@ class DatabaseFileViewModel(application: Application) : AndroidViewModel(applica
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(application.applicationContext)
}
+ val isDefaultDatabase: MutableLiveData by lazy {
+ MutableLiveData()
+ }
+
+ fun checkIfIsDefaultDatabase(databaseUri: Uri) {
+ IOActionTask(
+ {
+ (UriUtil.parse(PreferencesUtil.getDefaultDatabasePath(getApplication().applicationContext))
+ == databaseUri)
+ },
+ {
+ isDefaultDatabase.value = it
+ }
+ ).execute()
+ }
+
+ fun removeDefaultDatabase() {
+ IOActionTask(
+ {
+ PreferencesUtil.saveDefaultDatabasePath(getApplication().applicationContext,
+ null)
+ },
+ {
+ }
+ ).execute()
+ }
+
val databaseFileLoaded: MutableLiveData by lazy {
MutableLiveData()
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFilesViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFilesViewModel.kt
index 28e628589..a69d42895 100644
--- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFilesViewModel.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/DatabaseFilesViewModel.kt
@@ -1,7 +1,6 @@
package com.kunzisoft.keepass.viewmodels
import android.app.Application
-import android.app.backup.BackupManager
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
@@ -42,11 +41,8 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
fun setDefaultDatabase(databaseFile: DatabaseFile?) {
IOActionTask(
{
- val context = getApplication().applicationContext
- UriUtil.parse(PreferencesUtil.getDefaultDatabasePath(context))
- PreferencesUtil.saveDefaultDatabasePath(context, databaseFile?.databaseUri)
- val backupManager = BackupManager(context)
- backupManager.dataChanged()
+ PreferencesUtil.saveDefaultDatabasePath(getApplication().applicationContext,
+ databaseFile?.databaseUri)
},
{
checkDefaultDatabase()
diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt
index 65b8c9e5b..6d9df989c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/FileDatabaseInfo.kt
@@ -23,7 +23,6 @@ import android.content.Context
import android.net.Uri
import android.text.format.Formatter
import androidx.documentfile.provider.DocumentFile
-import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil
import java.io.Serializable
import java.text.DateFormat
@@ -58,7 +57,11 @@ class FileDatabaseInfo : Serializable {
}
private set
- fun getModificationString(): String? {
+ fun getLastModification(): Long? {
+ return documentFile?.lastModified()
+ }
+
+ fun getLastModificationString(): String? {
return documentFile?.lastModified()?.let {
if (it != 0L) {
DateFormat.getDateTimeInstance()
@@ -69,6 +72,10 @@ class FileDatabaseInfo : Serializable {
}
}
+ fun getSize(): Long? {
+ return documentFile?.length()
+ }
+
fun getSizeString(): String? {
return documentFile?.let {
Formatter.formatFileSize(context, it.length())
diff --git a/app/src/main/jni/argon2/argon2_jni.c b/app/src/main/jni/argon2/argon2_jni.c
index 64203f9e4..caada5e43 100644
--- a/app/src/main/jni/argon2/argon2_jni.c
+++ b/app/src/main/jni/argon2/argon2_jni.c
@@ -129,7 +129,7 @@ void throwExceptionF(JNIEnv *env, jclass exception, const char *format, ...) {
JNIEXPORT jbyteArray
JNICALL Java_com_kunzisoft_keepass_crypto_keyDerivation_Argon2Native_nTransformMasterKey(JNIEnv *env,
- jobject this, jbyteArray password, jbyteArray salt, jint parallelism, jint memory,
+ jobject this, jint type, jbyteArray password, jbyteArray salt, jint parallelism, jint memory,
jint iterations, jbyteArray secretKey, jbyteArray associatedData, jint version) {
argon2_context context;
@@ -169,7 +169,7 @@ JNICALL Java_com_kunzisoft_keepass_crypto_keyDerivation_Argon2Native_nTransformM
context.flags = ARGON2_DEFAULT_FLAGS;
context.version = (uint32_t) version;
- int argonResult = argon2_ctx(&context, Argon2_d);
+ int argonResult = argon2_ctx(&context, (argon2_type) type);
jbyteArray result;
if (argonResult != ARGON2_OK) {
diff --git a/app/src/main/jni/argon2/src/argon2.c b/app/src/main/jni/argon2/src/argon2.c
index 9d294fd21..bf73d3935 100644
--- a/app/src/main/jni/argon2/src/argon2.c
+++ b/app/src/main/jni/argon2/src/argon2.c
@@ -25,6 +25,7 @@
const char *argon2_type2string(argon2_type type, int uppercase) {
switch (type) {
+ default:
case Argon2_d:
return uppercase ? "Argon2d" : "argon2d";
case Argon2_i:
diff --git a/app/src/main/res/drawable/ic_reload_white_24dp.xml b/app/src/main/res/drawable/ic_reload_white_24dp.xml
new file mode 100644
index 000000000..020472b89
--- /dev/null
+++ b/app/src/main/res/drawable/ic_reload_white_24dp.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/src/main/res/drawable/notification_ic_device_unlock_24dp.xml b/app/src/main/res/drawable/notification_ic_device_unlock_24dp.xml
new file mode 100644
index 000000000..238cce5b4
--- /dev/null
+++ b/app/src/main/res/drawable/notification_ic_device_unlock_24dp.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/notification_ic_fingerprint_unlock_24dp.xml b/app/src/main/res/drawable/notification_ic_fingerprint_unlock_24dp.xml
new file mode 100644
index 000000000..a03cce62b
--- /dev/null
+++ b/app/src/main/res/drawable/notification_ic_fingerprint_unlock_24dp.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_entry_edit.xml b/app/src/main/res/layout/activity_entry_edit.xml
index e04e5ac54..285fa5bfc 100644
--- a/app/src/main/res/layout/activity_entry_edit.xml
+++ b/app/src/main/res/layout/activity_entry_edit.xml
@@ -33,27 +33,14 @@
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
-
-
-
-
-
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?attr/specialToolbarAppearance"
+ app:titleTextAppearance="@style/KeepassDXStyle.TextAppearance.Toolbar.Special.Title"
+ app:subtitleTextAppearance="@style/KeepassDXStyle.TextAppearance.Toolbar.Special.SubTitle"
+ app:layout_constraintTop_toTopOf="parent" />
@@ -79,24 +66,22 @@
-
+ app:layout_constraintBottom_toBottomOf="parent" />
-
-
-
+ android:layout_height="match_parent" />
diff --git a/app/src/main/res/layout/fragment_advanced_unlock.xml b/app/src/main/res/layout/fragment_advanced_unlock.xml
new file mode 100644
index 000000000..f9e736982
--- /dev/null
+++ b/app/src/main/res/layout/fragment_advanced_unlock.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_entry_edit_contents.xml b/app/src/main/res/layout/fragment_entry_edit_contents.xml
index 3fca6e1bd..2fb95c7f9 100644
--- a/app/src/main/res/layout/fragment_entry_edit_contents.xml
+++ b/app/src/main/res/layout/fragment_entry_edit_contents.xml
@@ -106,6 +106,7 @@
android:inputType="textPassword|textMultiLine"
android:importantForAccessibility="no"
android:importantForAutofill="no"
+ android:imeOptions="flagNoPersonalizedLearning"
android:ems="10"
android:maxLines="10"
android:hint="@string/entry_password"/>
diff --git a/app/src/main/res/layout/fragment_set_otp.xml b/app/src/main/res/layout/fragment_set_otp.xml
index 6ffc6e9d3..c644902b1 100644
--- a/app/src/main/res/layout/fragment_set_otp.xml
+++ b/app/src/main/res/layout/fragment_set_otp.xml
@@ -29,6 +29,18 @@
android:layout_height="wrap_content"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
+
+
+
diff --git a/app/src/main/res/layout/pref_dialog_input_numbers.xml b/app/src/main/res/layout/pref_dialog_input_numbers.xml
index 1e466e59c..8e725ccd3 100644
--- a/app/src/main/res/layout/pref_dialog_input_numbers.xml
+++ b/app/src/main/res/layout/pref_dialog_input_numbers.xml
@@ -46,14 +46,24 @@
app:layout_constraintTop_toBottomOf="@+id/explanation_text"
app:layout_constraintStart_toStartOf="parent"
android:minHeight="48dp"/>
+
+ android:inputType="number"
+ android:minHeight="48dp"
+ app:layout_constraintEnd_toStartOf="@+id/input_text_unit"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/switch_element" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_entry_contents.xml b/app/src/main/res/layout/view_entry_contents.xml
index c5922f214..48164dfa5 100644
--- a/app/src/main/res/layout/view_entry_contents.xml
+++ b/app/src/main/res/layout/view_entry_contents.xml
@@ -175,12 +175,6 @@
android:layout_height="wrap_content"
android:text="@string/entry_accessed"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
-
+ app:layout_constraintEnd_toEndOf="parent">
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/contribution.xml b/app/src/main/res/menu/contribution.xml
index b76875ff9..5c1671456 100644
--- a/app/src/main/res/menu/contribution.xml
+++ b/app/src/main/res/menu/contribution.xml
@@ -22,6 +22,6 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/database.xml b/app/src/main/res/menu/database.xml
index d94f86817..a7a182b8a 100644
--- a/app/src/main/res/menu/database.xml
+++ b/app/src/main/res/menu/database.xml
@@ -24,4 +24,9 @@
android:title="@string/menu_save_database"
android:orderInCategory="95"
app:showAsAction="ifRoom" />
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 51b1a3342..d31b3c541 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -103,7 +103,6 @@
هل أنت متأكد من أنك لا تريد استخدام أي مفتاح تشفير ؟
الإصدار %1$s
أضف عناصر جديدة إلى قاعدتك
- قم بفتح قاعدة بياناتك ببصمتك
إضافة حقول مخصصة
نسخ حقل
تأمين قاعدة البيانات
@@ -114,7 +113,7 @@
وظيفة اشتقاق المفتاح
مهلة التطبيق
مدة الانتظار قبل إقفال قاعدة البيانات
- تصفَّح الملفات بتثبيت مدير الملفات OpenIntents
+ المحرر الذي يقبل هاذا الفعل ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانتاج, فتح وحفض ملفات قاعدة البيانات.
بعض الأجهزة لا تسمح للتطبيقات باستعمال الحافظة.
مهلة الحافظة
مدة التخزين في الحافظة(إذا كان جهازك يدعمها)
@@ -249,7 +248,7 @@
الخلفية
دورات التحويل
توفر الدورات الاضافية ضد هجوم توليد التركيبات ،لكنها تبطئ التحميل والحفظ.
- مقدار الذاكرة (بالبايت) لاستخدامها في دالة اشتقاق المفتاح.
+ مقدار الذاكرة لاستخدامها في دالة اشتقاق المفتاح.
درجة التوازي (عدد العمليات) لدالة اشتقاق المفتاح.
مجموعات قبل
نمط التحديد
@@ -286,7 +285,7 @@
الخوارزمية
أرقام
العداد
- عين كلمة المرور للمرة الواحدة
+ كلمة المرور للمرة الواحدة
UUID
من أجل <strong>حماية خصوصيتا</strong>٫<strong> إصلاح العلل</strong>٫ <strong>إضافة مميزات</strong> <strong>وجعلنا نشطاء دائما</strong>٫ نحن نعتمد على <strong>مساهمتك</strong>.
خانة تأشير الملف المفتاحي
@@ -351,7 +350,7 @@
قاعدة البيانات مفتوحة
إعدادات الملء التلقائي
حرر المدخلة
- لفتح قاعدة البيانات بسرعة اربط كلمة المرور بالبصمة.
+ لفتح قاعدة البيانات بسرعة اربط كلمة المرور بالبصمة.
لإيجاد كلمة المرور، أدخل العنوان أو اسم المستخدم أو محتوى أحد الحقول.
المدخلات لإدارة معرفاتك الرقمية.
\n
@@ -405,7 +404,7 @@
يطالبك بتغيير المفتاح الرئيسي (بالأيام)
اقترح تجديد المفتاح الرئيسي (بالأيام)
اقترح التجديد
- حجم التأريخ ( بالبايت) لكل مدخل
+ حجم التأريخ لكل مدخل
الحجم الأقصى
عدد عناصر التأريخ لكل مدخل
أنشئ كلمة سر قوية
diff --git a/app/src/main/res/values-b+sr+Latn/strings.xml b/app/src/main/res/values-b+sr+Latn/strings.xml
index 40a1e8b8a..2977796bf 100644
--- a/app/src/main/res/values-b+sr+Latn/strings.xml
+++ b/app/src/main/res/values-b+sr+Latn/strings.xml
@@ -175,7 +175,7 @@
Forsiraj obnovu
Preporučiti promenu glavnog ključa (u danima)
Preporučiti obnavljanje
- Ograniči veličinu istorije (u bajtovima) po unosu
+ Ograniči veličinu istorije po unosu
Maksimalna veličina
Ograniči broja stavki istorije po unosu
Maksimalan broj
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 000000000..ce7d45bee
--- /dev/null
+++ b/app/src/main/res/values-bg/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Обратна връзка
+ Алгоритъм за криптиране
+ Криптиране
+ Сигурност
+ Главен ключ
+ Добави група
+ Редактирай
+ Добави
+ Приемам
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 79cd4a4aa..838ffb326 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -207,7 +207,7 @@
S\'executa l\'ordre…
Grau de paral·lelisme (és a dir, nombre de fils) fets servir per la funció de derivació de la clau.
Paral·lelisme
- Quantitat de memòria (en bytes) usada per la funció de derivació de la clau.
+ Quantitat de memòria usada per la funció de derivació de la clau.
Ús de la memòria
Amaga els enllaços trencats en la llista de bases de dades recents
Mostra la ubicació de les bases de dades recents
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 3d7245aed..4975e880a 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -19,12 +19,12 @@
Czech translation by Jan Vaněk
-->
Domovská stránka
- Androidová verze správce hesel KeePass
+ Implementace správce hesel KeePass pro Android
Přijmout
Přidat záznam
Přidat skupinu
Šifrovací algoritmus
- Časový limit aplikace
+ Časový limit
Doba nečinnosti, po které se aplikace zamkne
Aplikace
Nastavení aplikace
@@ -35,16 +35,16 @@
Některá zařízení nedovolují aplikacím používat schránku.
Nelze vyprázdnit schránku
Časový limit schránky
- Doba uchování ve schránce
+ Doba uchování ve schránce (je-li podporována zařízením)
Vyberte zkopírovat %1$s do schránky
- Načítání klíče databáze…
+ Načítám klíč databáze…
Databáze
- Dešifrování obsahu databáze…
+ Dešifruji obsah databáze…
Použít jako výchozí databázi
Číslice
Otevřít existující databázi
Poslední přístup
- Storno
+ Zrušit
Poznámky
Potvrďte heslo
Vytvořeno
@@ -55,27 +55,27 @@
Heslo
Uložit
Název
- URL adresa
+ URL
Uživatelské jméno
Arcfour proudová šifra není podporována.
KeePassDX nemůže zpracovat toto URI.
- Soubor se nedaří vytvořit
- Databázi nelze číst.
+ Soubor se nepodařilo vytvořit
+ Databázi se nepodařilo načíst.
Ujistěte se, že cesta je správná.
Zadejte jméno.
Vyberte soubor s klíčem.
Nedostatek paměti k načtení celé databáze.
Je třeba zvolit alespoň jeden způsob vytváření hesla.
- Zadaná hesla se neshodují.
+ Hesla se neshodují.
Příliš vysoký „Počet průchodů“. Nastavuji na 2147483648.
Je třeba, aby každý řetězec měl název kolonky.
- Do nastavení „Délka“ zadejte celé kladné číslo.
- Název pole
- Hodnota pole
+ Do pole „Délka“ zadejte celé kladné číslo.
+ Název kolonky
+ Hodnota kolonky
Správce souborů
- Vytvoř heslo
+ Generovat heslo
Potvrdit heslo
- Vytvořené heslo
+ Generované heslo
Název skupiny
Soubor s klíčem
Délka
@@ -83,8 +83,8 @@
Heslo
Nebylo možno načíst autentizační údaje.
Nesprávný algoritmus.
- Nedaří se rozpoznat formát databáze.
- Soubor s klíčem je prázdný.
+ Nepodařilo se rozpoznat formát databáze.
+ Soubor klíče je prázdný.
Délka
Velikost položek seznamu
Velikost textu v seznamu prvků
@@ -97,28 +97,28 @@
Nastavení
Nastavení databáze
Smazat
- Podpořit vývoj darem
+ Přispět darem
Upravit
Skrýt heslo
Zamknout databázi
Otevřít
Hledat
- Ukaž heslo
- Jít na URL
+ Ukázat heslo
+ Přejít na URL
Mínus
Nikdy
Žádné výsledky hledání
Pro otevření tohoto URL nainstalujte webový prohlížeč.
Neprohledávat položky v záloze
Vynechat skupiny „Záloha“ a \"Koš\" z výsledků vyhledávání
- Vytvářím novou databázi…
- Zpracování…
+ Zakládám novou databázi…
+ Pracuji…
Ochrana
- Ke změně v databázi potřebuje KeePassDX oprávnění pro zápis.
+ Ke změně v databáze potřebuje KeePassDX oprávnění pro zápis.
Odstranit
Rijndael (AES)
Kořen
- Počet šifrovacích průchodů
+ Transformační průchody
Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.
Ukládám databázi…
Místo
@@ -134,7 +134,7 @@
Verze %1$s
Databázi odemknete zadáním hesla a/nebo souboru s klíčem.
\n
-\nNezapomeňte si po každé úpravě zazálohovat kopii svého .kdbx souboru na bezpečné místo.
+\nNezapomeňte po každé úpravě zálohovat kopii svého .kdbx souboru na bezpečné místo.
- 5 sekund
- 10 sekund
@@ -155,41 +155,41 @@
Šifrování
Funkce pro tvorbu klíče
Rozšířené ASCII
- Umožnit
- Databázi se nedaří načíst.
- Klíč se nedaří načíst, zkuste snížit množství paměti, využívané funkcí pro tvorbu klíče.
- Službu automatického vyplňování se nedaří zapnout.
+ Povolit
+ Databázi se nepodařilo načíst.
+ Klíč se nepodařilo načíst, zkuste snížit \"využití paměti\" pro KDF.
+ Službu automatického vyplňování se nepodařilo zapnout.
Není možné přesunout skupinu do ní samotné.
Soubor nenalezen. Zkuste jej otevřít ze správce souborů.
Zobrazit uživatelská jména
- V seznamech položek zobrazit uživatelská jména
+ V seznamech záznamů zobrazit uživatelská jména
Kopie %1$s
Vyplňování formulářů
Zkopírovat
Přesunout
Vložit
- Storno
+ Zrušit
Chráněno před zápisem
Čtení a zápis
Chráněno před zápisem
- Algoritmus šifrování databáze užitý pro všechna data.
- Klíč pro šifrovací algoritmus je vytvořen transformací hlavního klíče skrze odvozovací funkci klíče s náhodně přidanou složkou, tzv. solí.
+ Algoritmus šifrování databáze použit pro všechna data.
+ Klíč pro šifrovací algoritmus je vytvořen transformací hlavního klíče pomocí funkce odvození klíče s náhodně přidanou složkou, tzv. solí.
Využití paměti
- Množství paměti (v bajtech) použitých funkcí pro odvození klíče.
+ Množství paměti použitých funkcí pro odvození klíče.
Souběžné zpracovávání
- Stupeň souběžného zpracovávání (počet vláken) použitý funkcí pro vytvoření klíče.
+ Stupeň souběžného zpracovávání (počet vláken) použitý funkcí pro odvození klíče.
Seřadit
Nejnižší první ↓
Skupiny první
Koš jako poslední
Nadpis
Uživatelské jméno
- Vytvoření
- Změna
+ Založeno
+ Změněno
Přístup
Varování
- Nepoužívejte v hesle pro databázový soubor znaky mimo znakovou sadu Latin-1 (nepoužívejte znaky s diakritikou).
- Pokračovat bez ochrany odemknutím heslem\?
+ Nepoužívejte v hesle pro databázový soubor znaky mimo znaky kódování textu (nerozpoznané znaky budou konvertovány na stejné písmeno).
+ Pokračovat bez ochrany odemknutí heslem\?
Pokračovat bez šifrovacího klíče\?
Šifrované heslo uloženo
Tato databáze zatím nemá uložené heslo.
@@ -198,7 +198,7 @@
Obecné
Automatické vyplnění
KeePassDX automatické vyplňování formulářů
- Přihlásit se pomocí KeePassDX
+ Přihlásit se s KeePassDX
Nastavit výchozí službu automatického vyplňování
Povolit rychlé automatické vyplňování formulářů v ostatních aplikacích
Délka generovaného hesla
@@ -206,28 +206,28 @@
Znaky hesla
Nastavit povolené znaky pro generátor hesel
Schránka
- Oznamování schránky
- Ukázat oznamení schránky o kopírování pole při prohlížení záznamu
+ Oznámení schránky
+ Ukázat oznámení schránky o kopírování pole při prohlížení záznamu
Vymazat historii schránky manuálně, pokud automatické vymazání schránky selže.
Zamknout
Zámek obrazovky
Při zhasnutí obrazovky uzamknout databázi
- Pokročilé odemčení
- Biometrické odemčení
+ Rozšířené odemknutí
+ Biometrické odemknutí
Nechá otevřít databázi snímáním biometrického údaje
Smazat šifrovací klíče
- Smazat všechny šifrovací klíče související s biometrickým rozlišením
+ Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí
Tuto funkci se nedaří spustit.
V zařízení je instalován Android %1$s, ale potřebná je verze %2$s a novější.
- Hardware nebyl rozpoznán.
+ Odpovídající hardware nebyl rozpoznán.
Název souboru
Cesta
Přiřadit hlavní klíč
- Vytvořit novou databázi
+ Založit novou databázi
Využití koše
Před smazáním přesune skupiny a položky do skupiny „Koš“
- Písmo položek
- Čitelnost znaků v položkách můžete přizpůsobit změnou písma
+ Písmo kolonek
+ Čitelnost znaků v kolonkách můžete přizpůsobit změnou písma
Důvěřovat schránce
Povolit kopírování hesla záznamu a chráněných položek do schránky
Varování: Schránka je sdílena všemi aplikacemi. Pokud jsou do ní zkopírovány citlivé údaje, mohl by se k nim dostat další software.
@@ -235,7 +235,7 @@
Popis databáze
Verze databáze
Text
- Aplikace
+ Rozhraní
Ostatní
Klávesnice
Magikeyboard
@@ -253,52 +253,49 @@
Založte svůj první soubor pro správu hesel.
Otevřít existující databázi
Otevřete svou dříve používanou databázi ze správce souborů a pokračujte v jejím používání.
- Přidejte položky do databáze
- Položky pomáhají se správou vašich digitálních identit.
-\n
+ Přidejte záznamy do databáze
+ Záznamy pomáhají se správou Vašich digitálních identit.
+\n
\nSkupiny (ekvivalent složek) organizují záznamy v databázi.
- Hledejte v položkách
- Zadejte název, uživatelské jméno nebo jiné položky k nalezení svých hesel.
- Odemknutí databáze biometricky
- Propojte své heslo s načtenou biometrikou pro rychlé odemknutí databáze.
- Upravit položku
- Přidejte ke své položce vlastní kolonky. Společná data mohou být sdílena mezi více různými kolonkami.
+ Hledat v záznamech
+ Zadejte název, uživatelské jméno nebo jiné kolonky k nalezení svých hesel.
+ Upravit záznam
+ Přidejte ke svému záznamu vlastní kolonky. Společná data mohou být sdílena mezi různými kolonkami záznamu odkazem.
Vytvořit silné heslo
- Vygenerujte silné heslo pro svou položku, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.
+ Generujte silné heslo pro svůj záznam, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.
Přidat vlastní kolonky
Registrovat další kolonku, zadat hodnotu a volitelně ji ochránit.
Odemknout databázi
Ochraňte svou databázi před zápisem
- Změnit režim otevírání pro dané sezení.
-\n
-\nV režimu pouze pro čtení zabráníte nechtěným změnám do databáze.
-\n V režimu zápisu je možné přidávat, mazat nebo měnit všechny prvky dle libosti.
- Zkopírujte kolonku
- Zkopírované kolonky lze vkládat kam chcete
-\n
+ Změnit režim otevírání pro dané sezení.
+\n
+\nV režimu \"pouze pro čtení\" zabráníte nechtěným změnám v databázi.
+\nV režimu \"zápisu\" je možné přidávat, mazat nebo měnit všechny prvky podle libosti.
+ Zkopírovat kolonku
+ Zkopírované kolonky lze vkládat podle libosti.
+\n
\nK vyplňování formulářů použijte svou oblíbenou metodu.
- Uzamkni databáze
- Rychlé uzamkni databázi. Je možné nastavit, aby se databáze zamkla po určitém čase a také po zhasnutí obrazovky.
+ Uzamknout databázi
+ Rychle uzamknout databázi. Je možné nastavit, aby se databáze zamkla po určitém čase a také po zhasnutí obrazovky.
Řazení položek
Vyberte řazení položek a skupin.
Zapojit se
- Zapojte se a pomozte zvýšit stabilitu, bezpečnost a přidávání dalších funkcí.
- Na rozdíl od mnoha aplikací pro správu hesel, tato je <strong>bez reklam</strong>", je "<strong>svobodný software pod copyleft licencí</strong> a nesbírá žádné osobní údaje na svých serverech bez ohledu na to, jakou verzi používáte.
- Zakoupením varianty „pro“ získáte přístup k tomuto <strong>vizuálnímu stylu</strong> a hlavně pomůžete <strong>uskutečnění komunitních projektů.</strong>
+ Zapojte se a pomozte zvýšit stabilitu, bezpečnost a doplnění dalších funkcí.
+ Na rozdíl od mnoha aplikací pro správu hesel je tato <strong>bez reklam</strong>, je \u0020<strong>svobodný software pod copyleft licencí</strong> a nesbírá žádné osobní údaje na svých serverech bez ohledu na to, jakou verzi používáte.
+ Zakoupením varianty \"pro\" získáte přístup k tomuto <strong>vizuálnímu stylu</strong> a hlavně pomůžete <strong>uskutečnění komunitních projektů.</strong>
Tento <strong>vizuální styl</strong> je k dispozici díky vaší štědrosti.
- Pro zajištění svobody nás všech a pokračování aktivity, počítáme s vaším <strong>přispěním.</strong>
- Tato funkce je <strong>ve vývoji</strong> a potřebuje váš <strong>příspěvek</strong>, aby byla brzy k dispozici.
+ Pro zajištění svobody nás všech a pokračování aktivity počítáme s Vaším <strong>přispěním.</strong>
+ Tato funkce je <strong>ve vývoji</strong> a potřebuje Váš <strong>příspěvek</strong>, aby byla brzy k dispozici.
Zakoupením <strong>pro</strong> varianty,
<strong>Zapojením se</strong>,
- povzbudíte vývojáře k přidávání <strong>nových funkcí</strong> a <strong>opravování chyb</strong> dle vašich připomínek.
- Mnohé díky za vaše přispění.
+ povzbudíte vývojáře k doplnění <strong>nových funkcí</strong> a <strong>opravám chyb</strong> dle vašich připomínek.
+ Mockrát děkujeme za Váš příspěvek.
Tvrdě pracujeme na brzkém vydání této funkce.
- Nezapomeňte aplikaci aktualizovat instalováním nových verzí.
+ Pamatujte na aktualizaci aplikace instalováním nových verzí.
Stáhnout
- Zapojit se
+ Přispět
ChaCha20
AES
- Argon2
Vzhled aplikace
Motiv vzhledu aplikace
Sada ikon
@@ -307,16 +304,16 @@
Magikeyboard
Magikeyboard (KeePassDX)
Magikeyboard nastavení
- Položka
+ Záznam
Časový limit
Doba uchování položky v Magikeyboardu
Informace o oznámení
Zobrazit oznámení, když je položka dostupná
- Položka
+ Záznam
%1$s dostupné v Magikeyboardu
%1$s
Vymazat při zavření
- Zavři databázi při zavření oznámení
+ Zavřít databázi při zavření oznámení
Vzhled
Vzhled klávesnice
Klávesy
@@ -329,37 +326,37 @@
Vymazat při ukončení
Uzamknout databázi, jakmile trvání schránky vyprší nebo po uzavření oznámení
Koš
- Výběr položky
- Při prohlížení záznamu ukázat na Magikeyboard pole položek
+ Výběr záznamu
+ Při prohlížení záznamu ukázat na Magikeyboard kolonky
Smazat heslo
Smaže heslo zadané po pokusu o připojení k databázi
Otevřít soubor
- Potomci uzlu
- Přidej uzel
- Přidej záznam
+ Podřazené prvky uzlu
+ Přidat uzel
+ Přidat záznam
Přidat skupinu
Informace o souboru
Checkbox hesla
Checkbox souboru s klíčem
- Přepni ukázání hesla
+ Opakovat přepnutí viditelnosti hesla
Ikona záznamu
Generátor hesel
Délka hesla
- Přidej pole
- Odeber pole
+ Přidat pole
+ Odebrat pole
UUID
Sem záznam přesunout nelze.
Sem záznam zkopírovat nelze.
- Ukaž počet záznamů
- Ukaž počet záznamů ve skupině
+ Ukázat počet záznamů
+ Ukázat počet záznamů ve skupině
Pozadí
Aktualizovat
- Zavři kolonky
- Nelze vytvořit databázi s tímto heslem a klíčem ze souboru.
- Pokročilé odemčení
+ Zavřít pole
+ Nelze vytvořit databázi s tímto heslem a souborem klíče.
+ Rozšířené odemknutí
Biometrika
- Automaticky otevřít biometrickou pobídku
- Automaticky žádat biometriku, je-li databáze nastavena k jejímu použití
+ Automaticky otevřít pobídku
+ Automaticky žádat rozšířené odemknutí, je-li databáze nastavena k jejímu použití
Zapnout
Vypnout
Hlavní klíč
@@ -375,7 +372,7 @@
OTP
Neplatná OTP tajnost.
Nejméně jeden přihlašovací údaj musí být zadán.
- Sem skupinu kopírovat nemůžete.
+ Sem skupinu kopírovat nelze.
Tajný klíč musí mít formát Base32.
Čítač musít být mezi %1$d a %2$d.
Interval musít být mezi %1$d a %2$d vteřinami.
@@ -387,14 +384,14 @@
Databáze obsahuje duplikátní UUID.
Opravit chybu založením nového UUID pro duplikáty a pokračovat\?
Databáze otevřena
- Kopírujte pole záznamů pomocí schránky Vašeho zařízení
- K snadnějšímu otevření databáze použijte pokročilé odemknutí
+ Kopírovat kolonky záznamů pomocí schránky svého zařízení
+ K snadnějšímu otevření databáze použijte rozšířené odemknutí
Komprese dat
Komprese dat snižuje velikost databáze
Maximální počet
Omezit počet položek v historii záznamu
Maximální velikost
- Omezit velikost historie na záznam (v bajtech)
+ Omezit velikost historie na záznam
Doporučit změnu
Dporučit změnu hlavního klíče (dny)
Vynutit změnu
@@ -416,20 +413,20 @@
Skupina Koš
Uložit databázi automaticky
Uložit databázi po každé důležité akci (v režimu \"Zápis\")
- Připojené soubory
+ Přílohy
Obnovit historii
Smazat historii
Akce auto-klávesy
- Akce klávesy \"Jít\" po stisknutí klávesy \"Položka\"
+ Akce klávesy \"Jít\" po stisknutí klávesy \"Kolonka\"
Stáhnout %1$s
Zahajuji…
Probíhá: %1$d%%
Dokončuji…
Kompletní!
Skrýt propadlé záznamy
- Propadlé záznamy nejsou ukázány
+ Propadlé záznamy nebudou ukázány
Kontakt
- Příspěvky
+ Příspění
Feedback
Snadné hledání
Při otevření databáze žádat hledání
@@ -439,23 +436,23 @@
Uchová informaci o tom, kde jsou uloženy soubory s klíči
Ukázat nedávné soubory
Ukázat umístění nedávných databází
- Skrýt špatné odkazy na databáze
- Skrýt nesprávné odkazy v seznamu nedávných databází
+ Skrýt chybné odkazy na databáze
+ Skrýt chybné odkazy v seznamu nedávných databází
Udělit právo zápisu pro uložení změn v databázi
KeePassDX © %1$d Kunzisoft je <strong>open source</strong> a <strong>bey reklam</strong>.
\nJe poskytován jak je, pod licencí <strong>GPLv3</strong>, bez jakékoli záruky.
Abychom si <strong>udrželi svoji svobodu</strong>, <strong>opravili chyby</strong>,<strong>doplnili funkce</strong> a <strong>byli vždy aktivní</strong>, počítáme s Vaším <strong>přispěním</strong>.
- Nedaří se vytvořit soubor s databází.
+ Nepodařilo se vytvořit soubor databáze.
Přidat přílohu
- Zahodit
- Zahodit změny\?
- Ověřit
+ Zavrhnout
+ Zavrhnout změny\?
+ Zkontrolovat
Nastavit správu One-Time hesla (HOTP / TOTP) pro založení tokenu požadovaného pro dvoufázové ověření (2FA).
Nastavit OTP
Automaticky navrhnout výsledky hledání z webové domény nebo ID aplikace
Samočinné hledání
- Ukáže tlačítko zámku v uživatelském rozhraní
- Ukázat tlačítko zámku
+ Zobrazí tlačítko zámku v uživatelském rozhraní
+ Zobrazit tlačítko zámku
Nastavení samovyplnění
Přístup k souboru zrušenému správcem souborů
Tento štítek již existuje.
@@ -472,14 +469,14 @@
Hledat v subdoméně
Tento text se s požadovanou položkou neshoduje.
Přidat položku
- Automaticky přepnout na předchozí klávesnici po provedení Akce auto-klávesy
+ Automaticky přepnout na předchozí klávesnici po provedení \"Akce auto-klávesy\"
Akce auto-klávesy
Automaticky přepnout zpět na předchozí klávesnici na obrazovce ověřovacích údajů databáze
Obrazovka ověřovacích údajů databáze
Přepnout klávesnici
Nahrát %1$s
- Info o údajích
- Databáze KeePassu předpokládá uchovávat jen malé pomocné sobory (např. PGP soubory).
+ Informace o údajích
+ Databáze KeePassu předpokládá uchovávat jen malé pomocné sobory (např. PGP soubory s klíči).
\n
\nVaše databáze by se mohla značně zvětšit a tímto nahráním tak ztratit na výkonnosti.
Nahráním tohoto souboru nahradíte existující soubor.
@@ -493,4 +490,67 @@
Odstraní přílohy obsažené v databázi, ale nikoli přílohy propojené se záznamem
Přidat přílohu
Nahrát přílohu k záznamu pro uložení důležitých externích dat.
+ Ukáže UUID propojené se záznamem
+ Ukázat UUID
+ Uložení dat není povoleno, je-li databáze v režimu pouze pro čtení.
+ Zeptat se na uložení dat, jakmile byl formulář přezkoušen
+ Zeptat se před uložením
+ Pokuste se uložit údaje hledání, když manuálně vybíráte položku
+ Uložit info hledání
+ Zavřít databázi po samovyplnění polí
+ Zavřít databázi
+ Po uzamknutí databáze automaticky přepnout zpět na předchozí klávesnici
+ Uzamknout databázi
+ Pokuste se uložit sdílené info, když manuálné vybíráte položku
+ Uložit sdílené info
+ Oznámení
+ Vyžadována aktualizace biometrického zabezpečení.
+ Žádné přihlašovací ani biometrické údaje nejsou registrovány.
+ Trvale odstranit všechny uzly z koše\?
+ Režim registrace
+ Režim ukládání
+ Režim vyhledávání
+ Jméno kolonky již existuje.
+ Uložení nové položky v režimu databáze pouze pro čtení není povoleno
+ Enter
+ Backspace
+ Vybrat záznam
+ Zpět na předchozí klávesnici
+ Vlastní kolonky
+ Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí\?
+ Dovolí pro otevření databáze použít heslo Vašeho zařízení
+ Odemknutí heslem zařízení
+ Heslo zařízení
+ Zadejte heslo a klikněte na tlačítko \"Rozšířené odemknutí\".
+ Nelze inicializovat pobídku pro rozšířené odemknutí.
+ Chyba při rozšířeném odemknutí: %1$s
+ Otisk pro rozšířené odemknutí nebyl rozpoznán
+ Nelze načíst klíč rozšířeného odemknutí. Prosím, smažte jej a opakujte proces rozpoznání odemknutí.
+ Načíst důvěrný údaj pomocí dat rozšířeného odemknutí
+ Otevřít databázi pomocí rozpoznání rozšířeného odemknutí
+ Varování: Pokud použijete rozpoznání rozšířeného odemknutí, musíte si i nadále pamatovat hlavní heslo.
+ Rozpoznání rozšířeného odemknutí
+ Pro uložení důvěrných údajů otevřete pobídku rozšířeného odemknutí
+ Pro odemknutí databáze otevřete pobídku rozšířeného odemknutí
+ Smazat klíč rozšířeného odemknutí
+ Rozšířené odemknutí databáze
+ Časový limit rozšířeného odemknutí
+ Trvání použití rozšířeného odemknutí než bude obsah téhož smazán
+ Za účelem rozšířeného odemknutí neukládat žádný šifrovaný obsah
+ Přechodné rozšířené odemknutí
+ Pro odstranění klíčů rozšířeného odemknutí klepnout
+ Argon2id
+ Argon2d
+ Abyste rychle odemknuli databázi, propojte své heslo s naskenovanou biometrikou nebo údaji zámku zařízení.
+ Vypršení pokročilého odemknutí
+ Obsah
+ Seznam nelze řádně sestavit.
+ URI databáze nelze načíst.
+ Návrhy samovyplnění přidány.
+ Návrhy inline
+ Pokusí ze zobrazit návrhy samovyplnění přímo z kompatibilní klávesnice
+ Přístup k souboru odebrán správcem souborů, uzavřete databázi a nově ji otevřete z jejího adresáře.
+ Databázi nově načíst
+ Přepsat externí změny uložením databáze nebo databázi včetně posledních změn nově načíst.
+ Informace obsažená ve Vašem databázovém souboru by změněna mimo aplikaci.
\ No newline at end of file
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 2482668f9..77911db5e 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -35,7 +35,7 @@
Nogle enheder, vil ikke lade programmer bruge udklipsholderen.
Kunne ikke rydde udklipsholderen
Udklipsholder timeout
- Varighed af opbevaring i udklipsholder
+ Varighed af opbevaring i udklipsholder (hvis det understøttes)
Vælg for at kopiere %1$s til udklipsholder
Opretter databasenøgle…
Database
@@ -174,7 +174,7 @@
Databasekrypteringsalgoritme anvendt for alle data.
For at generere nøglen til krypteringsalgoritmen, omdannes hovednøglen ved hjælp af en tilfældigt saltet nøgleafledningsfunktion.
Hukommelsesforbrug
- Hukommelse (i bytes), som anvendes af nøgleafledningsfunktion.
+ Hukommelse, som anvendes af nøgleafledningsfunktion.
Parallelitet
Grad af parallelitet (dvs. antallet af tråde), som anvendes af nøgleafledningsfunktion.
Sorter
@@ -206,7 +206,7 @@
Angiv tilladte tegn for adgangskodegenerator
Udklipsholder
Udklipsholdermeddelelser
- Aktivér udklipsholder for at kopiere felter når en post vises
+ Vis udklipsholder for at kopiere felter når en post vises
Hvis automatisk sletning af udklipsholder mislykkes, slet historikken manuelt.
Lås
Skærmlås
@@ -217,7 +217,7 @@
Slet krypteringsnøgler
Slet alle krypteringsnøgler, der er relateret til biometrisk genkendelse
Funktionen kunne ikke startes.
- Android-version %1$s opfylder ikke minimum versionskrav %2$s.
+ Enheden kører Android %1$s, men har brug for %2$s eller nyere.
Kunne ikke finde den tilsvarende hardware.
Filnavn
Sti
@@ -240,13 +240,13 @@
Magikeyboard
Aktiver et brugerdefineret tastatur, der udfylder adgangskoder og alle identitetsfelter
Tillad ingen hovednøgle
- Aktiver knappen \"Åbn\", hvis der ikke er valgt nogen legitimationsoplysninger
+ Tillader at trykke på knappen \"Åbn\", hvis der ikke er valgt nogen legitimationsoplysninger
Skrivebeskyttet
Åbn database skrivebeskyttet som standard
Pædagogiske tips
Fremhæv elementer for at lære, hvordan programmet fungerer
Nulstil pædagogiske tip
- Vis alle pædagogiske info igen
+ Vis al uddannelsesinformation igen
Nulstilling af pædagogiske tips
Opret databasefilen
Opret den første adgangskodeadministrationsfil.
@@ -258,8 +258,6 @@
\nGrupper (~mapper) organiserer poster i databasen.
Søg i poster
Indtast titel, brugernavn eller indhold af andre felter for at hente adgangskoder.
- Biometrisk oplåsning af databasen
- Knyt adgangskoden til det scannede biometri for hurtigt at låse databasen op.
Rediger posten
Rediger post med brugerdefinerede felter. Pool data kan refereres mellem forskellige indtastningsfelter.
Opret en stærk adgangskode
@@ -297,7 +295,6 @@
Bidrag
ChaCha20
AES
- Argon2
Tema
Tema, der bruges i programmet
Ikonpakke
@@ -394,7 +391,7 @@
Max. antal
Begræns antallet af historikposter pr. indtastning
Max. størrelse
- Begræns historikstørrelsen (i bytes) pr. post
+ Begræns historikstørrelsen pr. post
Anbefalet fornyelse
Anbefal ændring af hovednøglen (dage)
Gennemtving fornyelse
@@ -436,10 +433,10 @@
For at <strong>holde vores frihed</strong>, <strong>rette fejl</strong>, <strong>tilføje funktioner</strong> og <strong>at være altid aktiv</strong>, regner vi med <strong>bidrag</strong>.
Hurtig søgning
Anmod om en søgning når en database åbnes
- Gem placering af databaser
- Husk placeringen af databaser
- Gem placering af nøglefiler
- Husker placeringen af databasernøglefiler
+ Husk placeringer af databaser
+ Holder styr på, hvor databaserne gemmes
+ Husk placering af nøglefiler
+ Holder styr på, hvor nøglefiler gemmes
Vis seneste filer
Vis placeringer af de seneste databaser
Skjule brudte databaselinks
@@ -452,7 +449,7 @@
Kassér
Kasser ændringer\?
Valider
- Foreslår automatisk søgeresultater fra webdomænet eller applikationsId
+ Foreslår automatisk søgeresultater fra webdomænet eller applikations-id
Automatisk søgning
Viser låseknappen i brugergrænsefladen
Vis låseknap
@@ -467,10 +464,10 @@
Blokeringsliste for webdomæne
Blokeringsliste der forhindrer automatisk udfyldning af programmer
Blokeringsliste for program
- Skift automatisk tilbage til det forrige tastatur efter udførelse af automatisk tastehandling
+ Skift automatisk tilbage til det forrige tastatur efter udførelse af \"Automatisk tastehandling\"
Auto nøglehandling
Skift automatisk tilbage til det forrige tastatur på databasens legitimationsskærm
- Database legitimationsoplysninger skærm
+ Skærmbilledet til databaselegitimationsoplysninger
Skifte tastatur
Filter
Søg på webdomæner med begrænsninger på underdomæner
@@ -494,7 +491,6 @@
Fjerner vedhæftede filer indeholdt i databasen, men ikke knyttet til en post
Fjern ikke-sammenkædede data
Data
- Kryptoobjektet kunne ikke hentes.
Biometrisk sikkerhedsopdatering påkrævet.
Indholdet af nøglefilen bør aldrig ændres og bør i bedste fald indeholde tilfældigt genererede data.
Det anbefales ikke at tilføje en tom nøglefil.
@@ -511,4 +507,9 @@
Søgetilstand
Det er ikke tilladt at gemme et nyt element i en skrivebeskyttet database
Oplysninger om legitimationsoplysninger
+ Der er ikke tilmeldt biometriske legitimationsoplysninger eller enhedsoplysninger.
+ Overfør en vedhæftet fil til posten for at gemme vigtige eksterne data.
+ Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg
+ Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg
+ Feltnavnet findes allerede.
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4ae49bf53..fd44ec980 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -22,7 +22,7 @@
Translations from David Ramiro
-->
Kontakt
- Beitrag
+ Beiträge
Rückmeldung
Webseite
Android-Implementierung des Passwortmanagers KeePass
@@ -43,9 +43,9 @@
Zwischenablage-Timeout
Dauer der Speicherung in der Zwischenablage (falls vom Gerät unterstützt)
%1$s in die Zwischenablage kopieren
- Datenbank-Schlüsseldatei erzeugen…
+ Datenbank-Schlüsseldatei erzeugen …
Datenbank
- Entschlüsselung der Datenbankinhalte…
+ Entschlüsselung der Datenbankinhalte …
Als Standard-Datenbank verwenden
Zahlen
KeePassDX © %1$d Kunzisoft ist <strong>quelloffen</strong> und <strong>ohne Werbung</strong>.\nDie Nutzung der Software erfolgt auf eigene Verantwortung und ohne jegliche Garantie. Die Applikation wird unter den Bedingungen der <strong>GPLv3</strong> lizenziert.
@@ -97,7 +97,7 @@
Länge
Größe der Listenelemente
Schriftgröße der Listenelemente
- Datenbank wird geladen…
+ Datenbank wird geladen …
Kleinbuchstaben
Passwort verstecken
Passwörter standardmäßig mit (***) maskieren
@@ -118,12 +118,12 @@
Nie
Keine Suchergebnisse
Bitte einen Webbrowser installieren, um diese URL zu öffnen.
- Papierkorb/Backup nicht durchsuchen
- Die Gruppen „Backup“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt
+ Recycle bin und Backup nicht durchsuchen
+ Die Gruppen „Backup“ und „Recycle bin“ werden bei der Suche nicht berücksichtigt
Schnellsuche
Beim Öffnen einer Datenbank eine Suche veranlassen
- Neue Datenbank anlegen…
- Ausführen…
+ Neue Datenbank anlegen …
+ Ausführen …
Sicherheit
Schreibgeschützt
KeePassDX benötigt Schreibrechte, um etwas an der Datenbank zu ändern.
@@ -132,7 +132,7 @@
Start
Schlüsseltransformationen
Zusätzliche Schlüsseltransformationen bieten einen besseren Schutz gegen Wörterbuch- oder Brute-Force-Angriffe. Allerdings dauert dann auch das Laden und Speichern der Datenbank entsprechend länger.
- Datenbank wird gespeichert…
+ Datenbank wird gespeichert …
Leerzeichen
Suchen
Natürliche Sortierung
@@ -199,7 +199,7 @@
Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet.
Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird.
Speichernutzung
- Größe des Speichers (in Bytes) der für die Schlüsselableitung genutzt wird.
+ Größe des Speichers der für die Schlüsselableitung genutzt wird.
Parallelismus
Grad des Parallelismus (d. h. Anzahl der Threads), der für die Schlüsselableitung genutzt wird.
Sortieren
@@ -218,7 +218,7 @@
Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen
Zwischenablage
Verschlüsselungsschlüssel löschen
- Alle mit der biometrischen Erkennung verbundenen Verschlüsselungsschlüssel löschen.
+ Lösche alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen
Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.
Keine entsprechende Hardware.
Papierkorb-Nutzung
@@ -231,7 +231,7 @@
Datenbankbeschreibung
Datenbankversion
Text
- App
+ Interface
Andere
Tastatur
Magikeyboard
@@ -249,8 +249,6 @@
\nGruppen (wie Ordner) helfen, Einträge in der Datenbank zu ordnen.
Einträge durchsuchen
Titel, Benutzernamen oder Inhalte anderer Feldern eingeben, um die Passwörter wiederzufinden.
- Biometrische Datenbank-Entsperrung
- Verknüpft Ihr Passwort mit Ihrer gescannten Biometrie, um Ihre Datenbank schnell zu entsperren.
Eintrag bearbeiten
Bearbeiten Sie Ihren Eintrag mit persönlichen Feldern. Persönliche Felder können mit Querverweisen aus anderen Einträgen ergänzt werden.
Ein starkes Passwort erstellen
@@ -269,7 +267,7 @@
Mitmachen
Mithelfen, um Stabilität und Sicherheit zu verbessern und weitere Funktionen zu ermöglichen.
Anders als viele andere Passwortmanager ist dieser <strong>werbefrei</strong>, <strong>quelloffen</strong> und unter einer <strong>Copyleft-Lizenz</strong>. Es werden keine persönlichen Daten gesammelt, in welcher Form auch immer, unabhängig von der verwendeten Version (kostenlos oder Pro).
- Mit dem Kauf der Pro-Version erhält man Zugang zu diesem <strong>visuellen Stil</strong> und unterstützt insbesondere <strong>die Umsetzung gemeinschaftlicher Projektarbeiten.</strong>
+ Mit dem Kauf der Pro-Version erhalten Sie Zugriff auf diesen <strong>visuellen Stil</strong> und unterstützen insbesondere <strong>die Umsetzung gemeinschaftlicher Projekte.</strong>
Dieser <strong>visuelle Stil</strong> wurde wegen Ihrer Großzügigkeit freigeschaltet.
Um unsere Freiheit zu bewahren und immer aktiv zu bleiben, zählen wir auf Ihren <strong>Beitrag.</strong>
Diese Funktion ist <strong>in Entwicklung</strong> und erfordert <strong>Ihren Beitrag</strong>, um bald verfügbar zu sein.
@@ -278,12 +276,11 @@
Sie ermutigen die Entwickler:innen, <strong>neue Funktionen</strong> einzuführen und gemäß Ihren Anmerkungen <strong>Fehler zu beheben</strong>.
Vielen Dank für Ihre Unterstützung.
Wir bemühen uns, diese Funktion bald zu veröffentlichen.
- Vergessen Sie nicht, Ihre App aktuell zu halten, indem Sie neue Versionen installieren.
+ Denken Sie daran, Ihre App auf dem neuesten Stand zu halten, indem Sie neue Versionen installieren.
Download
Unterstützen
ChaCha20
AES
- Argon2
Symbolpaket
In der App verwendetes Symbolpaket
Eine Gruppe kann nicht in sich selbst verschoben werden.
@@ -302,10 +299,10 @@
Schreibgeschützt
Schreibschutz der Datenbank aktivieren
Datenbank standardmäßig schreibgeschützt öffnen
- Den Öffnungsmodus für die Sitzung ändern.
-\n
-\n„Schreibgeschützt“ verhindert unbeabsichtigte Änderungen an der Datenbank.
-\nMit „Änderbar“ können alle Elemente hinzugefügt, gelöscht oder geändert werden.
+ Den Öffnungsmodus für die Sitzung ändern.
+\n
+\n„Schreibgeschützt“ verhindert unbeabsichtigte Änderungen an der Datenbank.
+\nMit „Änderbar“ können Sie alle Elemente nach Belieben hinzufügen, löschen oder ändern.
Eintrag bearbeiten
Datenbank kann nicht geladen werden.
Laden des Schlüssels fehlgeschlagen. Bitte versuchen, die „Speicherplatznutzung“ von KDF zu verringern.
@@ -339,7 +336,7 @@
Speicherort zuletzt verwendeter Datenbanken anzeigen
Defekte Datenbankverknüpfungen ausblenden
Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden
- Nicht die App beenden…
+ App nicht beenden …
Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird
Beim Schließen löschen
Papierkorb
@@ -350,7 +347,7 @@
Datei öffnen
Eintrag hinzufügen
Gruppe hinzufügen
- Datei-Info
+ Dateiinformationen
Symbol für den Eintrag
Passwort-Generator
Passwortlänge
@@ -376,8 +373,8 @@
Biometrisch
Aktivieren
Deaktivieren
- Biometrische Abfrage automatisch öffnen
- Automatisch nach Biometrie fragen, wenn die Datenbank dafür eingerichtet ist
+ Abfrage automatisch öffnen
+ Automatisch nach der erweiterten Entsperrung fragen, wenn die Datenbank dafür eingerichtet ist
Hauptschlüssel
Sicherheit
Verlauf
@@ -397,7 +394,7 @@
Zeitraum muss zwischen %1$d und %2$d Sekunden liegen.
Token muss %1$d bis %2$d Stellen enthalten.
%1$s mit derselben UUID %2$s existiert bereits.
- Datenbank wird erstellt…
+ Datenbank wird erstellt …
Sicherheits-Einstellungen
Hauptschlüssel-Einstellungen
Die Datenbank enthält UUID-Duplikate.
@@ -410,7 +407,7 @@
Maximale Anzahl
Anzahl der Verlaufseinträge pro Eintrag begrenzen
Maximale Größe
- Verlaufsumfang (in Bytes) pro Eintrag begrenzen
+ Verlaufsumfang pro Eintrag begrenzen
Erneuerung empfehlen
(Nach Tagen) Änderung des Hauptschlüssels empfehlen
Erneuerung erzwingen
@@ -426,21 +423,21 @@
Die Datenbank konnte nicht gespeichert werden.
Datenbank speichern
Papierkorb leeren
- Befehl ausführen…
+ Befehl ausführen …
Sollen die ausgewählten Knoten wirklich gelöscht werden\?
Der Schlüsselspeicher ist nicht richtig initialisiert.
Papierkorb-Gruppe
Datenbank automatisch speichern
- Automatisches Speichern der Datenbank nach einer wichtigen Aktion (im Modus \"Bearbeiten\")
+ Automatisches Speichern der Datenbank nach einer wichtigen Aktion (im Modus „Bearbeiten“)
Anhänge
Historie wiederherstellen
Historie löschen
Auto-Key-Aktion
Aktion der Go-Taste, die automatisch nach dem Drücken einer Feldtaste ausgeführt wird
%1$s herunterladen
- Initialisieren…
+ Initialisieren …
Fortschritt: %1$d%%
- Fertigstellen…
+ Fertigstellen …
Vollständig!
Abgelaufene Einträge ausblenden
Abgelaufene Einträge werden nicht angezeigt
@@ -474,9 +471,9 @@
Gemeinsame Infos durchsuchen
Starten Sie die Anwendung, die das Formular enthält, neu, um die Sperrung zu aktivieren.
Automatisches Ausfüllen sperren
- Liste der Domains, auf denen ein automatisches Ausfüllen unterbunden wird
+ Liste der Domains, auf denen ein automatisches Ausfüllen unterlassen wird
Liste gesperrter Web-Domains
- Liste der Apps, in denen ein automatisches Ausfüllen unterbunden wird
+ Liste der Apps, in denen ein automatisches Ausfüllen verhindert wird
Liste gesperrter Anwendungen
Suche Web-Domains mit Subdomain-Beschränkungen
Subdomain-Suche
@@ -486,7 +483,7 @@
Tastatur wechseln
Auto-Key-Aktion
Datenbank-Anmeldebildschirm
- Automatisches Zurückschalten auf die vorherige Tastatur nach Ausführung der automatischen Tastenaktion
+ Nach Ausführung der automatischen Tastenaktion automatisch zur vorherigen Tastatur wechseln
Automatisches Zurückschalten zur vorherigen Tastatur auf dem Datenbank-Anmeldebildschirm
Laden Sie einen Anhang für Ihren Eintrag hoch, um wichtige externe Daten zu speichern.
Anmeldeinformationen
@@ -503,7 +500,7 @@
%1$s hochladen
Anhang hinzufügen
Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind
- Die Datei trotzdem hinzufügen\?
+ Soll die Datei trotzdem hinzugefügt werden\?
Zeigt die mit einem Eintrag verknüpfte UUID an
UUID anzeigen
Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.
@@ -511,7 +508,6 @@
Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank
Datenbank sperren
Benachrichtigung
- Kryptoobjekt kann nicht abgerufen werden.
Biometrisches Sicherheitsupdate erforderlich.
Es sind keine biometrischen oder Geräteanmeldeinformationen registriert.
Registrierungsmodus
@@ -526,4 +522,40 @@
Versuche bei manueller Eingabeauswahl gemeinsam genutzte Informationen zu speichern
Gemeinsam genutzte Informationen speichern
Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?
+ Der Feldname existiert bereits.
+ Warnung: Sie müssen sich immer noch an ihr Masterpasswort erinnern, wenn sie die erweiterte Entsperrerkennung verwenden.
+ Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen
+ Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank
+ Löschen des Schlüssels zum erweiterten Entsperren
+ Erweiterte Entsperrerkennung
+ Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.
+ Erweiterte Entsperrung der Datenbank
+ Verfallzeit der erweiterten Entsperrung
+ Dauer der erweiterten Entsperrung bevor sein Inhalt gelöscht wird
+ Verfall der erweiterten Entsperrung
+ Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen
+ Temporäre erweiterte Entsperrung
+ Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden
+ Drücken um erweiterte Entsperrschlüssel zu löschen
+ Inhalt
+ Öffne Datenbank mit erweiterter Entsperrerkennung
+ Eingabetaste
+ Rücktaste
+ Wähle Eintrag
+ Zurück zur vorherigen Tastatur
+ Benutzerdefinierte Felder
+ Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?
+ Geräteanmeldedaten entsperren
+ Geräteanmeldedaten
+ Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.
+ Initialisieren des erweitertes Entsperren Dialogs fehlgeschlagen.
+ Erweitertes Entsperren Fehler: %1$s
+ Konnte den Abdruck des erweiterten Entsperrens nicht erkennen
+ Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.
+ Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren
+ Argon2id
+ Argon2d
+ Die Liste kann nicht ordnungsgemäß neu erstellt werden.
+ Datenbank-URI kann nicht abgerufen werden.
+ Datenbank neu laden
\ No newline at end of file
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index dc947505b..7dee954af 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -165,7 +165,7 @@
Αλγόριθμος κρυπτογράφησης βάσης δεδομένων που χρησιμοποιείται για όλα τα δεδομένα.
Για να δημιουργηθεί το κλειδί για τον αλγόριθμο κρυπτογράφησης, το κύριο κλειδί μετασχηματίζεται χρησιμοποιώντας μια τυχαία αλατισμένη λειτουργία εξαγωγής κλειδιών.
Χρήση μνήμης
- Ποσότητα μνήμης (σε bytes) που θα χρησιμοποιηθεί από τη λειτουργία εξαγωγής κλειδιών.
+ Ποσότητα μνήμης που θα χρησιμοποιηθεί από τη λειτουργία εξαγωγής κλειδιών.
Παραλληλισμός
Βαθμός παραλληλισμού (δηλ. Αριθμός νημάτων) που χρησιμοποιείται από τη συνάρτηση εξαγωγής κλειδιών.
Ταξινόμηση
@@ -213,7 +213,7 @@
Περιγραφή Βάσης Δεδομένων
Έκδοση Βάσης Δεδομένων
Κείμενο
- Εφαρμογή
+ Διεπαφή
Άλλα
Πληκτρολόγιο
Magikeyboard
@@ -257,12 +257,11 @@
ενθαρρύνετε τους προγραμματιστές να δημιουργούν <strong>νέες λειτουργίες</strong> και να <strong>διορθώνουν σφάλματα</strong> σύμφωνα με τις παρατηρήσεις σας.
Ευχαριστούμε πολύ για τη συνεισφορά σας.
Εργαζόμαστε σκληρά για να διαθέσουμε αυτό το χαρακτηριστικό γρήγορα.
- Μην ξεχνάτε να ενημερώνετε την εφαρμογή σας, εγκαθιστώντας νέες εκδόσεις.
+ Θυμηθείτε να ενημερώνετε την εφαρμογή σας, εγκαθιστώντας νέες εκδόσεις.
Download
Συνεισφορά
ChaCha20
AES
- Argon2
Θέμα Εφαρμογής
Θέμα που χρησιμοποιείται στην εφαρμογή
Πακέτο Εικονιδίων
@@ -286,7 +285,7 @@
Αλλάξτε τη λειτουργία ανοίγματος για το session.
\n
\nΤο \"Προστατευμένο από εγγραφή\" αποτρέπει τυχόν μη επιθυμητές αλλαγές στη βάση δεδομένων.
-\nΤο \"Τροποποιητικό\" σάς επιτρέπει να προσθέσετε, να διαγράψετε ή να τροποποιήσετε όλα τα στοιχεία.
+\nΤο \"Τροποποιητικό\" σάς επιτρέπει να προσθέσετε, να διαγράψετε ή να τροποποιήσετε όλα τα στοιχεία όπως επιθυμείτε.
Επεξεργασία καταχώρησης
Δεν ήταν δυνατή η φόρτωση της βάσης δεδομένων σας.
Δεν ήταν δυνατή η φόρτωση του κλειδιού. Προσπαθήστε να μειώσετε την KDF \"Χρήση μνήμης\".
@@ -315,7 +314,7 @@
Λειτουργία επιλογής
Μη κλείσιμο της εφαρμογής …
Πατήστε \'Πίσω\' για να κλειδώσετε
- Κλείδωμα της βάσης δεδομένων όταν ο χρήστης κάνει κλικ στο κουμπί \"πίσω\" στη αρχική οθόνη
+ Κλείδωμα της βάσης δεδομένων όταν ο χρήστης κάνει κλικ στο κουμπί \"πίσω\" στην οθόνη προέλευσης
Καθαρισμός στο κλείσιμο
Κλείδωμα της βάσης δεδομένων όταν λήξει η διάρκεια του προχείρου ή η ειδοποίηση κλείσει αφού αρχίσετε να την χρησιμοποιείτε
Κάδος ανακύκλωσης
@@ -353,10 +352,10 @@
Προηγμένο ξεκλείδωμα
Βιομετρικό ξεκλείδωμα
Σας επιτρέπει να σαρώσετε το βιομετρικό σας για να ανοίξετε τη βάση δεδομένων
- Αυτόματο άνοιγμα βιομετρικής προτροπής
- Ζητήστε αυτόματα βιομετρία εάν η βάση δεδομένων έχει ρυθμιστεί για να τη χρησιμοποιήσει
+ Αυτόματο άνοιγμα προτροπής
+ Ζητήστε αυτόματα προηγμένο ξεκλείδωμα εάν η βάση δεδομένων έχει ρυθμιστεί για να το χρησιμοποιήσει
Διαγράψτε τα κλειδιά κρυπτογράφησης
- Διαγράψτε όλα τα κλειδιά κρυπτογράφησης που σχετίζονται με τη βιομετρική αναγνώριση
+ Διαγράψτε όλα τα κλειδιά κρυπτογράφησης που σχετίζονται με το προηγμένο ξεκλείδωμα
Ενεργοποίηση
Απενεργοποίηση
Κύριο κλειδί
@@ -393,7 +392,7 @@
Μέγιστος αριθμός
Περιορίστε τον αριθμό των στοιχείων ιστορικού ανά καταχώριση
Μέγιστο μέγεθος
- Περιορίστε το μέγεθος ιστορικού (σε bytes) ανά καταχώριση
+ Περιορίστε το μέγεθος ιστορικού ανά καταχώριση
Συστήστε ανανέωση
Προτεινόμενη αλλαγή του κύριου κλειδιού (ημέρες)
Εξαναγκαστική ανανέωση
@@ -407,8 +406,6 @@
Gzip
Ενεργοποιώντας ένα προσαρμοσμένο πληκτρολόγιο συγκεντρώνει τους κωδικούς πρόσβασής σας και όλα τα πεδία ταυτότητας
Ρυθμίσεις πληκτρολογίου συσκευής
- Βιομετρικό ξεκλείδωμα βάσης δεδομένων
- Συνδέστε τον κωδικό πρόσβασής σας στο σαρωμένο βιομετρικό σας για να ξεκλειδώσετε γρήγορα τη βάση δεδομένων σας.
Δεν ήταν δυνατή η αποθήκευση της βάσης δεδομένων.
Αποθήκευση βάσης δεδομένων
Αδειάστε τον κάδο ανακύκλωσης
@@ -468,7 +465,7 @@
Λίστα αποκλεισμού Εφαρμογών
Λίστα αποκλεισμού που αποτρέπει την αυτόματη συμπλήρωση εφαρμογών
Αυτόματη ενέργεια πλήκτρου
- Επιστρέψτε αυτόματα στο προηγούμενο πληκτρολόγιο μετά την εκτέλεση της ενέργειας του αυτόματου πλήκτρου
+ Επιστρέψτε αυτόματα στο προηγούμενο πληκτρολόγιο μετά την εκτέλεση της ενέργειας του \"Αυτόματου πλήκτρου\"
Επιστρέψτε αυτόματα στο προηγούμενο πληκτρολόγιο στην οθόνη διαπιστευτηρίων βάσης δεδομένων
Οθόνη διαπιστευτηρίων βάσης δεδομένων
Εναλλαγή πληκτρολογίου
@@ -506,7 +503,6 @@
Προσπαθήστε να αποθηκεύσετε κοινόχρηστες πληροφορίες όταν κάνετε μια χειροκίνητη επιλογή καταχώρησης
Αποθήκευση κοινόχρηστων πληροφοριών
Ειδοποίηση
- Δεν είναι δυνατή η ανάκτηση κρυπτογραφικού αντικειμένου.
Απαιτείται ενημέρωση βιομετρικής ασφάλειας.
Κανένα πιστοποιητικό βιομετρίας ή συσκευής δεν είναι εγγεγραμμένο.
Να διαγραφούν οριστικά όλοι οι κόμβοι από τον κάδο ανακύκλωσης;
@@ -514,4 +510,46 @@
Λειτουργία αποθήκευσης
Λειτουργία αναζήτησης
Η αποθήκευση ενός νέου αντικειμένου δεν επιτρέπεται σε μια βάση δεδομένων μόνο για ανάγνωση
+ Το όνομα πεδίου υπάρχει ήδη.
+ Προηγμένο ξεκλείδωμα αναγνώρισης
+ Προειδοποίηση: Θα πρέπει να θυμάστε τον κύριο κωδικό πρόσβασης εάν χρησιμοποιείτε προηγμένο ξεκλείδωμα αναγνώρισης.
+ Ανοίξτε τη βάση δεδομένων με προηγμένο ξεκλείδωμα αναγνώρισης
+ Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για αποθήκευση διαπιστευτηρίων
+ Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για να ξεκλείιδώσετε τη βάση δεδομένων
+ Διαγραφή προηγμένου κλειδιού ξεκλειδώματος
+ Enter
+ Backspace
+ Επιλέξτε καταχώριση
+ Επιστροφή στο προηγούμενο πληκτρολόγιο
+ Προσαρμοσμένα πεδία
+ Διαγραφή όλων των κλειδιών κρυπτογράφησης που σχετίζονται με το προηγμένο ξεκλείδωμα αναγνώρισης;
+ Σας επιτρέπει να χρησιμοποιήσετε τα διαπιστευτήρια της συσκευής σας για να ανοίξετε τη βάση δεδομένων
+ Ξεκλείδωμα διαπιστευτηρίων συσκευής
+ Διαπιστευτήρια συσκευής
+ Πληκτρολογήστε τον κωδικό πρόσβασης, και στη συνέχεια κάντε κλικ στο κουμπί \"Προηγμένο ξεκλείδωμα\".
+ Δεν είναι δυνατή η προετοιμασία προτροπής προηγμένου ξεκλειδώματος.
+ Δεν ήταν δυνατή η αναγνώριση αποτυπώματος προηγμένου ξεκλειδώματος
+ Προηγμένο ξεκλείδωμα σφάλμα: %1$s
+ Δεν είναι δυνατή η ανάγνωση του προηγμένου κλειδιού ξεκλειδώματος. Διαγράψτε το και επαναλάβετε τη διαδικασία αναγνώρισης ξεκλειδώματος.
+ Εξαγωγή διαπιστευτηρίων βάσης δεδομένων με προηγμένο ξεκλείδωμα δεδομένων
+ Συνδέστε τον κωδικό πρόσβασής σας με το σαρωμένο βιομετρικό ή τα διαπιστευτήρια της συσκευής σας για να ξεκλειδώσετε γρήγορα τη βάση δεδομένων σας.
+ Προηγμένο ξεκλείδωμα βάσης δεδομένων
+ Χρονικό όριο προηγμένου ξεκλειδώματος
+ Προσωρινό προηγμένο ξεκλείδωμα
+ Μην αποθηκεύετε κανένα κρυπτογραφημένο περιεχόμενο για να χρησιμοποιήσετε προηγμένο ξεκλείδωμα
+ Διάρκεια της χρήσης προηγμένου ξεκλειδώματος πριν την διαγραφή του περιεχομένου
+ Λήξη προηγμένου ξεκλειδώματος
+ Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος
+ Περιεχόμενα
+ Argon2id
+ Argon2d
+ Δεν είναι δυνατή η σωστή αναδημιουργία της λίστας.
+ Δεν είναι δυνατή η ανάκτηση του URI βάσης δεδομένων.
+ Προστέθηκαν προτάσεις αυτόματης συμπλήρωσης.
+ Απόπειρα εμφάνισης προτάσεων αυτόματης συμπλήρωσης απευθείας από ένα συμβατό πληκτρολόγιο
+ Προτάσεις στην ίδια γραμμή
+ Πρόσβαση στο αρχείο που ανακλήθηκε από το διαχειριστή αρχείων, κλείστε τη βάση δεδομένων και ανοίξτε το ξανά από τη θέση του.
+ Αντικαταστήστε τις εξωτερικές τροποποιήσεις αποθηκεύοντας τη βάση δεδομένων ή φορτώστε την ξανά με τις πιο πρόσφατες αλλαγές.
+ Οι πληροφορίες που περιέχονται στο αρχείο της βάσης δεδομένων σας έχουν τροποποιηθεί εκτός της εφαρμογής.
+ Επαναφόρτωση βάσης δεδομένων
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 71fd512da..2bfed9ce2 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -19,9 +19,9 @@
Spanish translation by José I. Paños. Updated by David García-Abad (23-09-2013)
-->
- Commentario
+ Comentarios
Página de inicio
- Implementación para Android del gestor de contraseñas KeePass
+ Implementación en Android del gestor de contraseñas KeePass
Aceptar
Añadir entrada
Añadir grupo
@@ -31,30 +31,30 @@
Aplicación
Configuración de la aplicación
Paréntesis
- Explora ficheros con OpenIntents File Manager
+ Se requiere un administrador de archivos que acepte la acciones de intención ACTION_CREATE_DOCUMENT y ACTION_OPEN_DOCUMENT para crear, abrir y guardar los archivos de la base de datos.
Portapapeles limpiado
Portapapeles caducado
- Duración del almacenamiento en el portapapeles (si lo admite el dispositivo)
+ Duración del almacenamiento en el portapapeles (si lo admite su dispositivo)
Seleccionar para copiar %1$s en el portapapeles
- Creando clave de la base de datos…
+ Recuperando la clave de la base de datos…
Base de datos
Descifrando el contenido de la base de datos…
- Utilice como base de datos por defecto
+ Utilizar como base de datos por defecto
Dígitos
- KeePassDX © %1$d Kunzisoft es \u0020de <strong>código abierto</strong> y <strong>sin publicidad</strong>.
+ KeePassDX © %1$d Kunzisoft es de <strong>código abierto</strong> y <strong>sin publicidad</strong>.
\nSe proporciona tal cual, bajo licencia <strong>GPLv3</strong>, sin ninguna garantía.
Abrir base de datos existente
- Acceso
+ Accedido
Cancelar
Notas
Confirmar contraseña
- Creación
- Caducidad
+ Creado
+ Caduca
Archivo de clave
- Modificación
+ Modificada
Contraseña
Guardar
- Nombre
+ Título
URL
Nombre de usuario
No se admite el cifrador de flujo Arcfour.
@@ -64,11 +64,11 @@
Asegúrese de que la ruta sea correcta.
Proporcione un nombre.
Seleccione un archivo de clave.
- No queda memoria para cargar toda la base de datos.
+ No hay memoria para cargar toda la base de datos.
Debe seleccionar al menos un tipo de generación de contraseñas.
Las contraseñas no coinciden.
Pasadas demasiado grande. Establecido a 2147483648.
- Proporcione un número entero positivo en el campo «Longitud».
+ Introduzca un número entero positivo en el campo \"Longitud\".
Explorador de archivos
Generar contraseña
Confirmar contraseña
@@ -78,7 +78,7 @@
Longitud
Contraseña
Contraseña
- Contraseña o archivo de clave no válido.
+ No se pudieron leer las credenciales.
No se pudo reconocer el formato de la base de datos.
Longitud
Tamaño de los elementos de la lista
@@ -103,7 +103,7 @@
Menos
Nunca
Sin resultado de búsqueda
- Instale un navegador web para abrir este URL.
+ Instale un navegador web para abrir esta URL.
No buscar en las entradas de respaldo
Omite los grupos «Respaldo» y «Papelera de reciclaje» de los resultados de búsqueda
Creando nueva base de datos…
@@ -124,7 +124,9 @@
No se admite esta versión de la base de datos.
Mayúsculas
Versión %1$s
- Introduzca una contraseña y/o un archivo de clave para desbloquear su base de datos.
+ Introduzca la contraseña y/o el archivo clave para desbloquear su base de datos.
+\n
+\nHaga una copia de seguridad de su archivo de base de datos en un lugar seguro después de cada cambio.
- 5 segundos
- 10 segundos
@@ -146,28 +148,28 @@
ASCII extendido
Permitir
Error del portapapeles
- Algunos dispositivos Android tienen un error en la implementación del portapapeles que provoca fallos al copiar desde las aplicaciones.
- Falló la limpieza del portapapeles
+ Algunos dispositivos no permiten que las aplicaciones usen el portapapeles.
+ No se pudo limpiar el portapapeles
Cada cadena debe tener un nombre de campo.
El servicio de autocompletado no se ha podido habilitar.
Nombre del campo
Valor del campo
No se pudo encontrar el archivo. Intente volver a abrirlo en el explorador de archivos.
- El algoritmo es incorrecto.
+ Algoritmo incorrecto.
El archivo de clave está vacío.
Copia de %1$s
Llenado de formulario
Protección
Protegida contra escritura
- KeePassDX necesita permiso de escritura para modificar la base de datos.
- Algoritmo de cifrado utilizado en todos los datos.
+ Dependiendo del administrador de archivos, puede que KeePassDX no permita escribir en su almacenamiento.
+ Algoritmo de cifrado utilizado para todos los datos.
Para generar la clave del algoritmo de cifrado, la clave maestra se transforma mediante una función de derivación de claves con una sal aleatoria.
Uso de memoria
- Cantidad de memoria (en bytes) que usará la función de derivación de clave.
+ Cantidad de memoria que usará la función de derivación de clave.
Paralelismo
Grado de paralelismo (p. ej. número de hilos) usados por la función de derivación de clave.
Ordenar
- Inferiores primero ↓
+ Más bajo primero ↓
Agrupar antes
Papelera debajo
Título
@@ -177,8 +179,8 @@
Acceso
Resultados de búsqueda
Atención
- Datos de entrada no encontrados.
- Evite emplear en la base de datos caracteres que no pertenezcan al formato de codificación del texto (los caracteres no reconocidos se convierten a la misma letra).
+ No se pudieron encontrar los datos de entrada.
+ Evite los caracteres de la contraseña fuera del formato de codificación de texto en el archivo de la base de datos (los caracteres no reconocidos se convierten a la misma letra).
¿Continuar sin la protección de desbloqueo de contraseña\?
¿Continuar sin clave de cifrado\?
Contraseña cifrada almacenada
@@ -197,25 +199,25 @@
Establecer los caracteres permitidos del generador de contraseñas
Portapapeles
Notificaciones del portapapeles
- Mostrar notificaciones del portapapeles para copiar campos al examinar una entrada
+ Mostrar las notificaciones del portapapeles para copiar campos al examinar una entrada
Bloquear
Bloqueo de pantalla
Bloquear la base de datos cuando la pantalla esté apagada
Desbloqueo avanzado
Desbloqueo biométrico
- Le permite escanear su huella dactilar u otro dato biométrico para abrir la base de datos
+ Le permite escanear sus datos biométricos para abrir la base de datos
Eliminar claves de cifrado
- Eliminar todas las claves de cifrado relacionadas con el reconocimiento biométrico
+ Eliminar todas las claves de cifrado relacionadas con el reconocimiento de desbloqueo avanzado
No se pudo iniciar esta funcionalidad.
- La versión de Android del dispositivo es %1$s, pero es necesaria la %2$s o posterior.
- No se encontró el hardware correspondiente.
+ El dispositivo funciona con Android %1$s, pero necesita %2$s o posterior.
+ No se pudo encontrar el hardware correspondiente.
Nombre del archivo
Ruta
Asignar una clave maestra
- Crear base de datos nueva
+ Crear una base de datos nueva
Uso de la papelera de reciclaje
Mueve los grupos y las entradas al grupo \"Papelera de reciclaje\" antes de eliminarlos
- Fuente de los campos
+ Tipografía del campo
Cambiar la fuente de los campos para una mejor visibilidad del carácter
Portapapeles de confianza
Permitir copiar la contraseña de entrada y los campos protegidos al portapapeles
@@ -223,11 +225,11 @@
Descripción de la base de datos
Versión de la base de datos
Texto
- Aplicación
+ Interfaz
Otro
Teclado
Teclado mágico
- Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente.
+ Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente
Restablecer sugerencias didácticas
Mostrar de nuevo toda la información didáctica
Se restablecieron las sugerencias didácticas
@@ -241,8 +243,6 @@
\nLos grupos (~carpetas) organizan las entradas en su base de datos.
Busque registros fácilmente
Busque entradas por título, nombre de usuario u otros campos para recuperar fácilmente sus contraseñas.
- Desbloquee su base de datos con su huella digital
- Vincule la contraseña a su escaneo biométrico para desbloquear rápidamente la base de datos.
Editar la entrada
Edite la entrada con campos personalizados, puede agregar referencias a los datos de la agrupación entre campos de diferentes entradas.
Crear una contraseña segura
@@ -261,29 +261,28 @@
Participar
Participe para aumentar la estabilidad, la seguridad y agregar más funciones.
A diferencia de muchas aplicaciones de administración de contraseñas, esta es <strong>sin publicidad</strong>, <strong>software libre con copyleft</strong> y no recopila datos personales en sus servidores, sin importar la versión que use.
- Al comprar la versión pro, tendrá acceso a <strong>la característica visual </strong>y usted ayudará especialmente a <strong>la realización de proyectos comunitarios.</strong>
- Esta <strong>característica visual </strong>está disponible gracias a tu generosidad.
+ Al comprar la versión pro, tendrá acceso al <strong>estilo visual </strong>y ayudará especialmente a <strong>la realización de proyectos comunitarios.</strong>
+ Este <strong>estilo visual </strong>está disponible gracias a tu generosidad.
Para mantener nuestra libertad y estar siempre vigente, contamos con tu <strong>contribución.</strong>
Esta función está <strong>en desarrollo</strong> y requiere de tu <strong>contribución</strong> para estar disponible dentro de poco.
Al comprar la versión <strong>pro</strong>,
Al <strong>contribuir</strong>,
- usted alienta a los desarrolladores a crear <strong>nuevas funciones</strong> y a <strong>errores de configuración</strong> de acuerdo con tus comentarios.
+ anima a los desarrolladores a crear <strong>nuevas funciones</strong> y a <strong>corregir errores</strong> de acuerdo con sus comentarios.
Muchas gracias por tu contribución.
Estamos trabajando duro para lanzar esta característica rápidamente.
- No olvide mantener su aplicación actualizada.
+ Recuerde mantener su aplicación actualizada instalando nuevas versiones.
Descargar
Contribuir
ChaCha20
- AES-KDF
- Argon2
- Tema de aplicación
+ AES
+ Tema de la aplicación
Tema utilizado en la aplicación
Seleccione un paquete de iconos
Cambiar el paquete de iconos en la aplicación
Editar entrada
No se pudo cargar la base de datos.
No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.
- No se puede mover un grupo dentro de sí mismo.
+ No puede mover un grupo dentro de sí mismo.
Enseña nombres de usuario
Enseña nombres de usuador en las listras de entradas
Copiar
@@ -294,13 +293,13 @@
Modificable
Compilación %1$s
Si la eliminación del cortapapeles falla, elimine su historial manualmente.
- ATENCIÓN: todas las aplicaciones comparten el portapapeles. Si copia datos confidenciales, otros programas podrían recuperarlos.
+ Advertencia: El portapapeles es compartido por todas las aplicaciones. Si se copian datos sensibles, otros programas pueden recuperarlos.
No permitir claves maestras
- Activar el botón «Abrir» si no se selecciona ninguna credencial
- Pantallas didácticas
- Resaltar los elementos para conocer cómo funciona la aplicación
+ Permite pulsar el botón \"Abrir\" si no se seleccionan credenciales
+ Sugerencias educativas
+ Destacar elementos para aprender cómo funciona la aplicación
Protegida contra escritura
- Abrir su base de datos protegida contra escritura de manera predeterminada
+ Abrir la base de datos de solo lectura por defecto
Proteja la base de datos contra escritura
Teclado mágico
Teclado mágico (KeePassDX)
@@ -318,56 +317,56 @@
Apariencia
Tema del teclado
Teclas
- Vibrar al pulsar
- Emitir sonido al pulsar
+ Vibrar al pulsar tecla
+ Sonar al pulsar tecla
Modo de selección
No cierre la aplicación…
- Bloquear la base de datos cuando se pulsa el botón Atrás en la pantalla inicial
+ Bloquear la base de datos cuando el usuario pulse el botón atrás en la pantalla inicial
Vaciar al cerrar
- Bloquear la base de datos cuando la duración del portapapeles expire o la notificación sea cerrada
+ Bloquear la base de datos cuando expire la duración del portapapeles o cuando se cierre la notificación después de empezar a utilizarla
Papelera de reciclaje
Selección de entrada
Mostrar campos de entrada en el Teclado mágico al ver una entrada
Eliminar contraseña
- Elimina la contraseña proporcionada tras un intento de conexión
+ Elimina la contraseña introducida tras un intento de conexión a una bse de datos
Abrir archivo
- Elementos secundarios del nodo
+ Nodos hijo
Añadir nodo
Añadir entrada
Añadir grupo
- Información de archivo
- Casilla de contraseña
- Casilla de archivo de clave
+ Información del archivo
+ Casilla de verificación de la contraseña
+ Casilla de verificación del archivo de clave
Icono de entrada
Generador de contraseñas
Longitud de contraseña
Añadir campo
- Eliminar campo
+ Eliminar el campo
UUID
- No se puede mover una entrada aquí.
- No se puede copiar una entrada aquí.
- Mostrar cantidad de entradas
- Mostrar la cantidad de entradas en un grupo
+ No puede mover una entrada aquí.
+ No puede copiar una entrada aquí.
+ Mostrar el número de entradas
+ Mostrar el número de entradas en un grupo
Fondo
Actualizar
Cerrar campos
No se puede crear la base de datos con esta contraseña y este archivo de clave.
Desbloqueo avanzado
Biometría
- Abrir petición de datos biométricos automáticamente
- Abrir automáticamente la petición de datos biométricos cuando se define una clave biométrica para una base de datos
+ Abrir petición automáticamente
+ Solicitar automáticamente el desbloqueo avanzado si la base de datos está configurada para utilizarlo
Activar
Desactivar
Cambiar el modo de apertura de la sesión.
\n
\n\"Protegido contra escritura\" evita cambios no deseados en la base de datos.
-\n\"Modificable\" le permite agregar, eliminar o modificar todos los elementos.
- Presione hacia atrás en la raíz para bloquear
+\n\"Modificable\" le permite agregar, eliminar o modificar todos los elementos como desee.
+ Presione \'Atrás\' para bloquear
Repetir la visibilidad de la contraseña
- Llave maestra
+ Clave maestra
Seguridad
Historial
- Configuración de contraseña de un solo uso
+ Establecer una contraseña de un solo uso
Tipo de contraseña de un solo uso
Secreto
Período (segundos)
@@ -380,16 +379,16 @@
Clave secreta debe estar en formato Base32.
Contador debe estar entre %1$d y %2$d.
No se puede guardar la base de datos.
- Este texto no encaja con la información requerida.
+ Este texto no coincide con el elemento requerido.
No fue posible crear el archivo de base de datos.
- Parar lograr <strong>mantener nuestra libertad</strong>, <strong>corregir errores</strong>, <strong>agregar características</strong> y <strong>siempre estar activos</strong>, contamos con tu <strong>contribución</strong>.
+ Parar lograr <strong>mantener nuestra libertad</strong>, <strong>corregir errores</strong>, <strong>agregar características</strong> y <strong>estar siempre activos</strong>, contamos con tu <strong>contribución</strong>.
Añadir elemento
- Descarga completa!
+ ¡Completado!
Finalizando…
En progreso: %1$d%%
Inicializando…
Descargar %1$s
- Guardar la base de datos tras acciones importantes (en modo \"Modificable\")
+ Guardar la base de datos después de cada acción importante (en modo \"Modificable\")
Guardar base de datos automáticamente
Bloquear autocompletado
Cambiar teclado
@@ -398,12 +397,12 @@
Ninguna
Compresión
Nombre de usuario predeterminado
- Requerir cambiar la contraseña maestra la próxima vez (una sola vez)
+ Requerir cambiar la contraseña maestra la próxima vez (una vez)
Forzar renovación la próxima vez
- Requerir un cambio de contraseña maestra (días)
+ Requerir un cambio de la contraseña maestra (días)
Forzar renovación
Tamaño máximo
- Usar desbloqueo avanzado para abrir una base de datos de manera más fácil
+ Usar el desbloqueo avanzado para abrir una base de datos más fácilmente
Muestra el botón de bloqueo en la interfaz
Mostrar botón de bloqueo
Configuración de autocompletado
@@ -412,8 +411,8 @@
Otorga acceso de escritura para guardar cambios en la base de datos
Mostrar ubicaciones de bases de datos recientes
Mostrar archivos recientes
- Recordar la ubicación de las bases de datos
- Guardar ubicaciones de bases de datos
+ Lleva un registro de los lugares donde se almacenan las bases de datos
+ Recordar las ubicaciones de las bases de datos
La base de datos contiene UUIDs duplicados.
Restaurar historial
Vaciar papelera de reciclaje
@@ -422,12 +421,12 @@
%1$s con la misma UUID %2$s ya existe.
Esta etiqueta ya existe.
Descartar
- ¿Descartar cambios\?
+ ¿Descartar los cambios\?
Validar
Contribución
- Contactar
- Período debe estar entre %1$d y %2$d segundos.
- No se puede copiar un grupo aquí.
+ Contacto
+ El período debe estar entre %1$d y %2$d segundos.
+ No puede copiar un grupo aquí.
La compresión de datos reduce el tamaño de la base de datos
Compresión de datos
No se recomienda agregar un archivo de claves vacío.
@@ -436,19 +435,19 @@
Al cargar este archivo reemplazará el existente.
¿Borrar los nodos seleccionados de forma permanente\?
El acceso al archivo fue revocado por el administrador de archivos
- Ejecutando el comando …
+ Ejecutando el comando…
Ocultar enlaces rotos en la lista de bases de datos recientes
Ocultar enlaces de bases de datos rotos
Realiza un seguimiento de dónde se almacenan los archivos de claves
- Recuerdar las ubicaciones de los archivos de claves
+ Recordar las ubicaciones de los archivos de clave
Buscar dominios web con restricciones de subdominios
Búsqueda de subdominio
Solicite una búsqueda al abrir una base de datos
Búsqueda rápida
- Borrar historial
- El token debe tener de %1$d a %2$d dígitos.
+ Eliminar el historial
+ El token debe contener de %1$d a %2$d dígitos.
Archivos adjuntos
- Adjuntar
+ Añadir el archivo adjunto
Información de credenciales
Base de datos abierta
Adjuntar
@@ -458,4 +457,94 @@
Se supone que una base de datos KeePass contiene solo pequeños archivos de utilidad (como archivos de clave PGP).
\n
\nLa base de datos puede volverse muy grande y reducir su rendimiento con esta subida.
+ Grupo de la papelera de reciclaje
+ Filtrar
+ Elimina los archivos adjuntos contenidos en la base de datos pero no vinculados a una entrada
+ Eliminar los datos no vinculados
+ Datos
+ ¿Borrar todas las claves de encriptación relacionadas con el reconocimiento de desbloqueo avanzado\?
+ Tiempo límite de desbloqueo avanzado
+ Duración del uso de desbloqueo avanzado antes de borrar su contenido
+ Expiración de desbloqueo avanzado
+ No almacenar ningún contenido encriptado para utilizar el desbloqueo avanzado
+ Desbloqueo avanzado temporal
+ Le permite usar la credenciales de su dispositivo para abrir la base de datos
+ Desbloqueo de las credenciales del dispositivo
+ Toque para eliminar las teclas de desbloqueo avanzadas
+ Contenido
+ Copiar los campos de entrada usando el portapapeles de su dispositivo
+ Credenciales del dispositivo
+ Introduzca la contraseña y luego haga clic en el botón \"Desbloqueo avanzado\".
+ No se pudo inicializar el indicador de desbloqueo avanzado.
+ Error de desbloqueo avanzado: %1$s
+ No se pudo reconocer la impresión de desbloqueo avanzado
+ No se pudo leer la llave de desbloqueo avanzada. Por favor, bórrela y repita el procedimiento de reconocimiento de desbloqueo.
+ Extraer la credencial de la base de datos con datos de desbloqueo avanzado
+ Abrir la base de datos con reconocimiento de desbloqueo avanzado
+ Advertencia: Aún debes recordar tu contraseña maestra si usas el reconocimiento de desbloqueo avanzado.
+ Reconocimiento de desbloqueo avanzado
+ Abrir el indicador de desbloqueo avanzado para almacenar las credenciales
+ Abrir el aviso de desbloqueo avanzado para desbloquear la base de datos
+ El almacén de claves no está debidamente inicializado.
+ Se requiere una actualización de la seguridad biométrica.
+ No se ha inscrito ninguna credencial biométrica o del dispositivo.
+ El contenido del archivo clave nunca debe modificarse y, en el mejor de los casos, debe contener datos generados al azar.
+ ¿Borrar permanentemente todos los nodos de la papelera de reciclaje\?
+ Modo de registro
+ Modo de guardado
+ Modo de búsqueda
+ ¿Resolver el problema generando nuevos UUID para que los duplicados continúen\?
+ Borrar la clave de desbloqueo avanzado
+ El nombre del campo ya existe.
+ Guardar un nuevo elemento no está permitido en una base de datos de sólo lectura
+ Recomendar la renovación
+ Limitar el tamaño del historial por entrada
+ Limitar el número de elementos del historial por entrada
+ Número máximo
+ Configuración del teclado del dispositivo
+ Color personalizado de la base de datos
+ Recomendar cambiar la contraseña maestra (días)
+ Notificación
+ Ocultar las entradas expiradas
+ Buscar información compartida
+ Argon2id
+ Argon2d
+ Subir %1$s
+ Configurar la gestión de contraseñas de una sola vez (HOTP / TOTP) para generar un token solicitado para la autenticación de dos factores (2FA).
+ Establecer la contaseña de un solo uso
+ Vincule su contraseña con su credencial biométrica o del dispositivo escaneada para desbloquear rápidamente su base de datos.
+ Desbloqueo avanzado de la base de datos
+ No se permite guardar datos en una base de datos abierta como de sólo lectura.
+ Reiniciar la aplicación que contiene el formulario para activar el bloqueo.
+ Lista de bloqueo que impide el llenado automático de los dominios web
+ Lista de bloqueo de los dominios web
+ Lista de bloqueo que impide el llenado automático de las aplicaciones
+ Lista de bloqueo de las aplicaciones
+ Pedir que se guarden los datos cuando se valide un formulario
+ Pedir que se guarden los datos
+ Intente guardar la información de la búsqueda cuando haga una selección de entrada manual
+ Guardar la información de la búsqueda
+ Sugerir automáticamente los resultados de la búsqueda desde el dominio web o el ID de la aplicación
+ Búsqueda automática
+ Cerrar la base de datos después de una selección de autocompletado
+ Cerrar la base de datos
+ Entrar
+ Retroceder
+ Seleccionar la entrada
+ Volver al teclado anterior
+ Campos personalizados
+ Cambiar automáticamente al teclado anterior después de bloquear la base de datos
+ Bloquear la base de datos
+ Cambiar automáticamente al teclado anterior después de ejecutar \"Acción de la tecla automática\"
+ Acción de la tecla automática
+ Cambiar automáticamente al teclado anterior en la pantalla de credenciales de la base de datos
+ Pantalla de credenciales de la base de datos
+ Acción de la tecla automática
+ Intentar guardar la información compartida cuando hagas una selección de entrada manual
+ Guardar información compartida
+ Buscar automáticamente la información compartida para llenar el teclado
+ Muestra el UUID vinculado a una entrada
+ Mostrar UUID
+ No es posible reconstruir adecuadamente la lista.
+ La URI de la base de datos no puede ser recuperada.
\ No newline at end of file
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index e2902554e..8121e9be1 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -37,7 +37,7 @@
در حال ذخیره پایگاه داده
درجه موازی سازی (یعنی تعداد موضوعات) که توسط عملکرد مشتق کلیدی استفاده می شود.
موازی کاری
- مقدار حافظه (در بایت) که توسط تابع مشتق کلید مورد استفاده قرار گیرد.
+ مقدار حافظه که توسط تابع مشتق کلید مورد استفاده قرار گیرد.
استفاده از حافظه
دورهای رمزگذاری اضافی محافظت بالاتری در برابر حملات نیروی وحشی ایجاد می کنند ، اما در واقع می توانند سرعت و بارگذاری را کاهش دهند.
دور تحول
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 82c8be006..95957ab83 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -30,7 +30,7 @@
Ohjelman asetukset
Hakasulkeet
Tietokantojen avaamista, luomista ja tallentamista varten tarvitaan tiedostonhallintaohjelma, joka tukee ACTION_CREATE_DOCUMENT ja ACTION_OPEN_DOCUMENT Intent-toimintoja.
- Leikepöytä tyhjennetty.
+ Leikepöytä tyhjennetty
Leikepöytävirhe
Jotkin laitteet eivät anna sovellusten käyttää leikepöytää.
Leikepöytää ei voitu tyhjentää
@@ -71,7 +71,7 @@
Salasanat eivät täsmää.
Kierroksia on liian paljon. Asetetaan se arvoon 2147483648.
Jokaisella tekstillä tulee olla kentässä nimi.
- Syötä positiivinen kokonaisluku pituus-kenttään
+ Syötä positiivinen kokonaisluku pituus-kenttään.
Kentän nimi
Kentän arvo
Tiedostoselain
@@ -161,7 +161,7 @@
Turvallisuus
Muokkaa merkintää
Ainakin yksi pääsytieto tulee olla asetettuna.
- Avainta ei pystytty lataamaan. Kokeile vähentää KDF \"Muistin käyttöä\".
+ Avainta ei pystytty lataamaan. Kokeile vähentää KDF ”Muistin käyttöä”.
Tietokantaa ei pystytty avaamaan.
Virheellinen OTP salaisuus.
OTP
@@ -232,7 +232,7 @@
Luota leikepöytään
Vaihda fontti, jota käytetään kentissä parantaaksesi merkkien näkyvyyttä
Kenttäfontti
- Siirrä ryhmät ja tietueet \"Roskakori\" ryhmään ennen poistamista
+ Siirrä ryhmät ja tietueet ”Roskakori” ryhmään ennen poistamista
Roskakorin käyttö
Aseta pääavain
Polku
@@ -249,7 +249,7 @@
Salasanamerkit
Asettaa oletuspituuden generoiduille salasanoille
Rinnakkaisuus
- Avaimen johtamisfunktion käyttämän muistin (tavuina) määrä.
+ Avaimen johtamisfunktion käyttämän muistin määrä.
Muistin käyttö
Pääavain muunnetaan käyttäen satunnaista suolattua avaimen johtamisfunktiota, jotta salausalgoritmin avain voidaan generoida.
Salasanatietokannan salausalgoritmi, jota käytetään kaikelle datalle.
@@ -324,4 +324,4 @@
Et voi siirtää ryhmää itsensä sisälle.
Automaattista täyttöä ei voitu ottaa käyttöön.
Solmun lapset
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 9561da61c..0275a145c 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -33,7 +33,7 @@
Remplissage de formulaire
Parenthèses (ou autres)
ASCII étendu
- Un gestionnaire de fichiers qui accepte l\'action d\'intention ACTION_CREATE_DOCUMENT et ACTION_OPEN_DOCUMENT est nécessaire pour créer, ouvrir et enregistrer des fichiers de base de données.
+ Un gestionnaire de fichiers qui accepte l’action d’intention ACTION_CREATE_DOCUMENT et ACTION_OPEN_DOCUMENT est nécessaire pour créer, ouvrir et enregistrer des fichiers de base de données.
Autoriser
Presse-papier vidé
Erreur de presse-papier
@@ -133,7 +133,7 @@
Tours de transformation
Des tours de chiffrement supplémentaires fournissent une protection plus élevée contre les attaques par force brute, mais cela peut considérablement ralentir les opérations de chargement et d’enregistrement.
Utilisation de la mémoire
- Quantité de mémoire (en octets) à utiliser par la fonction de dérivation de clé.
+ Quantité de mémoire à utiliser par la fonction de dérivation de clé.
Parallélisme
Degré de parallélisme (nombre de fils d’exécution) utilisé par la fonction de dérivation de clé.
Enregistrement de la base de données…
@@ -205,7 +205,7 @@
Description de la base de données
Version de la base de données
Texte
- Application
+ Interface
Autres
Clavier
Magiclavier
@@ -225,8 +225,6 @@
\nLes groupes (≈dossiers) organisent les entrées dans votre base de données.
Rechercher dans les entrées
Saisir le titre, le nom d’utilisateur ou le contenu des autres champs pour récupérer vos mots de passe.
- Déverrouillage biométrique de la base de données
- Associe votre mot de passe à votre empreinte biométrique numérisée pour déverrouiller rapidement votre base de données.
Modifier l’entrée
Modifie votre entrée avec des champs personnalisés. La collection des données peut être référencée entre différents champs de l’entrée.
Créer un mot de passe fort
@@ -264,7 +262,6 @@
Twofish
ChaCha20
AES
- Argon2
- 5 secondes
- 10 secondes
@@ -299,7 +296,7 @@
Déplacer
Coller
Annuler
- Autoriser l\'absence de clé principale
+ Autoriser l’absence de clé principale
Autorise l’appui du bouton « Ouvrir » si aucun identifiant n’est sélectionné
Protéger en écriture
Modifiable
@@ -309,7 +306,7 @@
Changez le mode d’ouverture pour la session.
\n
\n« Protégé en écriture » empêche les modifications involontaires de la base de données.
-\n« Modifiable » vous permet d’ajouter, de supprimer ou de modifier tous les éléments.
+\n« Modifiable » vous permet d’ajouter, de supprimer ou de modifier tous les éléments comme vous le souhaitez.
Modifier l’entrée
Impossible de charger votre base de données.
Impossible de charger la clé. Veuillez essayer de diminuer l’utilisation mémoire de la fonction de dérivation de clé.
@@ -336,9 +333,9 @@
Appui clavier audible
Changement de clavier
Écran des identifications de la base de données
- Revenir automatiquement au clavier précédent sur l\'écran des identifications de la base de données
+ Revenir automatiquement au clavier précédent sur l’écran des identifications de la base de données
Action de touche automatique
- Revenir automatiquement au clavier précédent après avoir exécuté \"Action de touche automatique\"
+ Revenir automatiquement au clavier précédent après avoir exécuté « Action de touche automatique »
Mode sélection
Veuillez ne pas tuer l’application…
Appuyer sur « Retour » pour verrouiller
@@ -378,12 +375,12 @@
Désactiver
Biométrie
Ouvrir automatiquement l’invite biométrique
- Demande automatiquement la reconnaissance biométrique si la base de données est configurée pour l\'utiliser
+ Demande automatiquement la reconnaissance biométrique si la base de données est configurée pour l’utiliser
Clé principale
Sécurité
Historique
Configuration d’un mot de passe à usage unique
- Type OTP
+ Type MDP à usage unique
Secret
Période (secondes)
Compteur
@@ -409,9 +406,9 @@
Compression de données
La compression des données réduit la taille de la base de données
Nombre maximum
- Limite le nombre d\'éléments de l’historique par entrée
+ Limite le nombre d’éléments de l’historique par entrée
Taille maximum
- Limite la taille de l’historique (en octets) par entrée
+ Limite la taille de l’historique par entrée
Recommander le renouvellement
Recommande le changement de la clé principale (jours)
Forcer le renouvellement
@@ -434,8 +431,8 @@
Enregistrement automatique de la base de données
Enregistre la base de données après chaque action importante (en mode « Modifiable »)
Attachements
- Restaurer l\'historique
- Effacer l\'historique
+ Restaurer l’historique
+ Effacer l’historique
Action de touche automatique
Action de la touche « Go » après avoir appuyé sur une touche « Champ »
Téléchargement %1$s
@@ -449,7 +446,7 @@
Contribution
Afin de <strong>garder notre liberté</strong>, <strong>corriger les bugs</strong>, <strong>ajouter des fonctionnalités</strong> et <strong>être toujours actif</strong>, nous comptons sur votre <strong>contribution</strong>.
Recherche rapide
- Demande une recherche lors de l\'ouverture d\'une base de données
+ Demande une recherche lors de l’ouverture d’une base de données
Mémoriser l’emplacement des bases de données
Garde en mémoire l’emplacement où les bases de données sont stockées
Mémoriser les emplacements des fichiers clé
@@ -466,25 +463,25 @@
Abandonner
Abandonner les modifications \?
Valider
- Suggère automatiquement des résultats de recherche à partir du domaine Web ou de l\'application ID
+ Suggérer automatiquement des résultats de recherche à partir du domaine Web ou de l’identifiant de l’application
Recherche automatique
- Affiche le bouton de verrouillage dans l\'interface utilisateur
+ Affiche le bouton de verrouillage dans l’interface utilisateur
Afficher le bouton de verrouillage
Paramètres de remplissage automatique
Accès au fichier révoqué par le gestionnaire de fichiers
Ce label existe déjà.
- Redémarrez l\'application contenant le formulaire pour activer le blocage.
+ Redémarrez l’application contenant le formulaire pour activer le blocage.
Blocker le remplissage automatique
Liste de blocage qui empêche le remplissage automatique des domaines Web
Liste de blocage de domaine Web
Liste de blocage qui empêche le remplissage automatique des applications
- Liste de blocage d\'application
+ Liste de blocage d’application
Recherche automatiquement les informations partagées pour remplir le clavier
Rechercher les informations partagées
Filtre
Recherche des domaines Web avec des contraintes de sous-domaines
Recherche de sous-domaine
- Ce texte ne correspond pas à l\'élément demandé.
+ Ce texte ne correspond pas à l’élément demandé.
Ajouter un élément
Téléverser %1$s
Téléverse une pièce-jointe à votre entrée pour enregistrer d’importantes données externes.
@@ -497,30 +494,71 @@
Informations des identifiants
Supprimer les données non-liées peut réduire la taille de votre base de données mais peut également supprimer des données utilisées par des extensions KeePass.
Supprimer ces données quand même ?
- Il n\'est pas recommandé d\'ajouter un fichier clé vide.
+ Il n’est pas recommandé d’ajouter un fichier clé vide.
Le contenu du fichier clé ne devrait jamais changer, et dans le meilleur des cas, devrait contenir des données générées aléatoirement.
Supprimer les données non-liées
Supprimer les pièces jointes contenues dans la base de données mais non-liées à une entrée
Données
- Affiche l\'UUID lié à une entrée
- Afficher l\'UUID
- L\'enregistrement des données n\'est pas autorisé pour une base de données ouverte en lecture seule.
- Demande à enregistrer des données lorsqu\'un formulaire est validé
+ Affiche l’UUID lié à une entrée
+ Afficher l’UUID
+ L’enregistrement des données n’est pas autorisé pour une base de données ouverte en lecture seule.
+ Demande à enregistrer des données quand un formulaire est validé
Demander à enregistrer des données
- Essaie d\'enregistrer les informations de recherche lors de la sélection manuelle d\'une entrée
+ Essayer d’enregistrer les informations de recherche lors de la sélection manuelle d’une entrée
Enregistrer les infos de recherche
Ferme la base de données après une sélection de remplissage automatique
Fermer la base de données
Revient automatiquement au clavier précédent après le verrouillage de la base de données
Verrouiller la base de données
- Essaie d\'enregistrer les informations partagées lors de la sélection manuelle d\'une entrée
+ Essayer d’enregistrer les informations partagées lors de la sélection manuelle d’une entrée
Enregistrer les infos partagées
Notification
- Impossible de récupérer l\'objet crypto.
Mise à jour de sécurité biométrique requise.
Supprimer définitivement tous les nœuds de la corbeille \?
Mode enregistrement
Mode sauvegarde
Mode recherche
- L\'enregistrement d\'un nouvel élément n\'est pas autorisé dans une base de données en lecture seule
+ L’enregistrement d’un nouvel élément n’est pas autorisé dans une base de données en lecture seule
+ Le nom du champ existe déjà.
+ Supprimer toutes les clés de chiffrement liées à la reconnaissance de déverrouillage avancée \?
+ Vous permet d\'utiliser les informations d\'identification de votre appareil pour ouvrir la base de données
+ Déverrouillage par identifiants de l\'appareil
+ Déverouillage de l\'appareil
+ Tapez le mot de passe, puis cliquez sur le bouton \"Déverrouillage avancé\".
+ Impossible d\'initialiser l\'invite de déverrouillage avancé.
+ Erreur de déverrouillage avancé : %1$s
+ Impossible de reconnaître l\'empreinte de déverrouillage avancé
+ Impossible de lire la clé de déverrouillage avancé. Veuillez la supprimer et répéter la procédure de reconnaissance de déverrouillage.
+ Extraire les identifiants de la base de données avec des données de déverrouillage avancées
+ Ouvrir la base de données avec la reconnaissance de déverrouillage avancée
+ Attention : Vous devez toujours vous souvenir de votre mot de passe principal si vous utilisez la reconnaissance de déverrouillage avancée.
+ Reconnaissance de déverrouillage avancée
+ Ouvrez l\'invite de déverrouillage avancé pour stocker les informations d\'identification
+ Ouvrez l\'invite de déverrouillage avancé pour déverrouiller la base de données
+ Supprimer la clé de déverrouillage avancé
+ Entrer
+ Retour arrière
+ Sélection d\'une entrée
+ Retour au clavier précédent
+ Champs customisés
+ Lier votre mot de passe à vos informations d\'identification biométriques ou de périphérique scannées pour déverrouiller rapidement votre base de données.
+ Déverrouillage avancé de la base de données
+ Délai du déverrouillage avancé
+ Durée d\'utilisation du déverrouillage avancé avant de supprimer son contenu
+ Expiration du déverrouillage avancé
+ Ne stocker aucun contenu crypté pour utiliser le déverrouillage avancé
+ Déverrouillage avancé temporaire
+ Appuyez pour supprimer les clés de déverrouillage avancées
+ Contenu
+ Argon2id
+ Argon2d
+ Impossible de reconstruire correctement la liste.
+ L\'URI de la base de données ne peut pas être récupéré.
+ Suggestions de remplissage automatique ajoutées.
+ Tente d\'afficher des suggestions de remplissage automatique directement à partir d\'un clavier compatible
+ Suggestions en ligne
+ Écraser les modifications externes en sauvegardant la base de données ou recharger-la avec les dernières modifications.
+ Accès au dossier révoqué par le gestionnaire de fichiers, fermer la base de données et la rouvrir à partir de son emplacement.
+ Les informations contenues dans votre fichier de base de données ont été modifiées en dehors de l\'application.
+ Recharger la base de données
\ No newline at end of file
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index f631a1f9a..d5db3826d 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -27,7 +27,7 @@
Algoritmo de cifrado
Función de derivación de chave
Tempo de espera da aplicación
- Tempo antes de bloquear a base de datos cando a aplicación está inactiva.
+ Tiempo de inactividad antes de bloquear la base de datos
Aplicación
Parénteses
ASCII extendido
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index aba004a6f..ada9f8ff9 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -175,7 +175,7 @@
Trajno izbrisati odabrane čvorove\?
Verzija %1$s
Izgradnja %1$s
- Šifrirana lozinka pohranjena
+ Šifrirana lozinka spremljena
Povijest
Izgled
Opće
@@ -195,7 +195,7 @@
Biometrijsko otključavanje
Otvaranje baze podataka skeniranjem biometrike
Izbriši ključeve šifriranja
- Izbriši sve ključeve šifriranja povezane s biometrijskim prepoznavanjem
+ Izbriši sve ključeve šifriranja povezane s naprednim prepoznavanjem otključavanja
Nije moguće pronaći odgovarajući hardver.
Ime datoteke
Putanja
@@ -222,7 +222,7 @@
Proizvoljna boja baze podataka
Verzija baze podataka
Tekst
- Aplikacija
+ Sučelje
Ostalo
Komprimiranje
Bez
@@ -239,7 +239,7 @@
Nema dovoljno memorije za učitavanje cijele baze podataka.
Nije moguće učitati bazu podataka.
Nije moguće učitati ključ. Pokušaje smanjiti uporabu memorije KDF.
- Barem jedna akreditacija mora biti postavljena.
+ Barem jedan skup podataka za prijavu mora biti postavljen.
Svaki niz mora imati ime polja.
Nije moguće aktivirati uslugu automatskog ispunjavanja.
Nije moguće premjestiti grupu u samu sebe.
@@ -250,7 +250,7 @@
Nije moguće spremiti bazu podataka.
Razdoblje mora biti između %1$d i %2$d sekundi.
Nije moguće pronaći datoteku. Probaj je ponovo otvoriti iz upravitelja datoteka.
- Nije moguće čitati akreditacije.
+ Nije moguće čitati podatke za prijavu.
Veličina elemenata popisa
Veličina teksta u popisu elemenata
Mala slova
@@ -266,13 +266,13 @@
Ovisno o upravitelju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.
Riješiti problem generiranjem novih UUID-ova za duplikate\?
Korijen
- Količina memorije (u bajtovima) koju će koristiti funkcija za generiranje ključeva.
+ Količina memorije koju će koristiti funkcija za generiranje ključeva.
Ne zatvaraj aplikaciju …
Zadnji pristup
Posebni znakovi
Podcrtaj
Velika slova
- Ova baza podataka još nema spremljenu akreditaciju.
+ Ova baza podataka još nema spremljene podatke za prijavu.
Biometrija
Prijavi se s KeePassDX
Aktiviraj automatsko ispunjavanje za brzo ispunjavanje obrazaca u drugim aplikacijama
@@ -286,7 +286,7 @@
Premijesti grupe i unose u koš za smeće prije brisanja
Grupa koša za smeće
Ograniči broj spremljenih povijesti po unosu
- Ograniči veličinu povijesti (u bajtovima) po unosu
+ Ograniči veličinu povijesti po unosu
Povjerenje međuspremniku
Dozvoli kopiranje lozinke i zaštićenih polja u međuspremnik
Upozorenje: Međuspremnik dijele sve aplikacije. Ako se kopiraju osjetljivi podaci, druga aplikacija ih može vidjeti.
@@ -312,7 +312,7 @@
Vibracija tipki
Zvuk tipki
Dozvoli bez lozinke
- Dozvoljava dodir gumba „Otvori”, ako nijedna akreditacija nije odabrana
+ Dozvoljava dodir gumba „Otvori”, ako nisu odabrani nikoji podaci za prijavu
Izbriši lozinku
Briše upisanu lozinku nakon pokušaja povezivanja s bazom podataka
Zaštićeno od pisanja
@@ -366,7 +366,6 @@
Istek vremena za brisanje unosa tipkovnicom
Zaštiti bazu podataka od pisanja
Popis blokiranja web domena
- Biometrijsko otključavanje baze podataka
AES
Doprinos
Ova oznaka već postoji.
@@ -377,7 +376,7 @@
Dodatni prolazi šifriranja pružaju veću zaštitu od brutalnih napada, ali stvarno mogu usporiti učitavanje i spremanje.
Traži web domene s ograničenjima poddomena
Traži dijeljene informacije
- Ne zaboravi aktualizirati aplikaciju najnovijim verzijama.
+ Redovito aktualiziraj aplikaciju instaliranjem najnovijih verzija.
Za aktiviranje blokiranja, ponovo pokreni aplikaciju koja sadrži obrazac.
Odaberi način razvrstavanja unosa i grupa.
Pristup datoteci koju je opozvao upravitelj datoteka
@@ -395,13 +394,12 @@
<strong>Doprinosom</strong>,
Dodaj prilagođena polja
Zaključaj bazu podataka brzo, aplikaciju možeš postaviti tako da bazu nakon nekog vremena zaključa i kad se ekran isključi.
- Argon2
Twofish
Prikaži mjesto nedavnih baza podataka
- Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.
+ Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.
Kako bismo zadržali našu slobodu i uvijek bili aktivni, računamo na tvoj<strong>doprinos.</strong>
Za stvaranje ključa za algoritam šifriranja, glavni ključ se transformira pomoću funkcije za generiranje ključeva koja sadrži slučajnu komponentu.
- Zaključaj bazu podataka kad korisnik pritisne gumb za natrag na ekranu
+ Zaključaj bazu podataka kad korisnik pritisne gumb za natrag na glavnom ekranu
Sakrij pokvarene poveznice baze podataka
Blokiranje automatskog ispunjavanja
Baza ključeva nije ispravno inicijalizirana.
@@ -409,9 +407,9 @@
Istekli unosi se ne pokazuju
Zaključaj bazu podataka
Otključaj bazu podataka
- Automatski traži biometriju, ako je baza podataka tako postavljena
+ Automatski zatraži napredno otključavanje ako je baza podataka tako postavljena
Nije moguće pokrenuti ovu funkciju.
- Automatski otvori biometrijsku prijavu
+ Automatski otvori prozor za prijavu
Istek vremena međuspremnika
Pretraži unose
Kopiraj jedno polje
@@ -455,10 +453,10 @@
\nGrupe (~mape) organiziraju unose u bazi podataka.
U tijeku: %1$d%%
Gotovo!
- Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja automatske radnje tipke
+ Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja „Automatska radnje tipke”
Automatska radnja tipke
- Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka
- Ekran za unos podataka za prijavu u bazu podataka
+ Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu na bazu podataka
+ Ekran za unos podataka za prijavu na bazu podataka
Promijeni tipkovnicu
Baza podataka za KeePass trebala bi sadržavati samo male datoteke uslužnih programa (poput PGP datoteke ključeva).
\n
@@ -475,7 +473,7 @@
Ukloni nepovezane podatke
Podaci
Svejedno ukloniti ove podatke\?
- Podaci podataka prijave
+ Podaci za prijavu
Pokušaj spremiti podatke pretrage prilikom odabira ručnog unosa
Obavijest
Nije dopušteno spremati novi element u zaštićenoj bazi podataka
@@ -492,10 +490,50 @@
Pokušaj spremiti dijeljene informacije prilikom odabira ručnog unosa
Spremi dijeljene informacije
Trajno izbrisati sve čvorove iz smeća\?
- Nije moguće dohvatiti kripto objekt.
Potrebno je aktualizirati biometrijsku zaštitu.
Ne postoji biometrijski ključ niti podaci za prijavu uređaja.
Modus registracije
Modus spremanja
Modus pretrage
+ Ime polja već postoji.
+ Izbrisati sve ključeve šifriranja povezane s naprednim prepoznavanjem otključavanja\?
+ Upiši lozinku i zatim pritisni gumb „Napredno otključavanje”.
+ Otvori bazu podataka pomoću naprednog prepoznavanja otključavanja
+ Upozorenje: Ako koristiš prepoznavanje naprednog otključavanja morat ćeš i dalje znati glavnu lozinku.
+ Izbriši ključ naprednog otključavanja
+ Napredno prepoznavanje otključavanja
+ Nije moguće pokrenuti prozor naprednog otključavanja.
+ Greška naprednog otključavanja: %1$s
+ Izdvoji podatake za prijavu na bazu podataka pomoću podataka naprednog otključavanja
+ Otvori prozor naprednog otključavanja za spremanje podataka za prijavu
+ Otvori prozor naprednog otključavanja za otključavanje baze podataka
+ Nije moguće prepoznati digitanlni otisak za napredno otključavanje
+ Nije moguće pročitati ključ naprednog otključavanja. Izbriši ga i ponovi postupak prepoznavanja.
+ Tipka Enter
+ Tipka Backspace
+ Odaberi unos
+ Natrag na prethodnu tipkovnicu
+ Prilagođena polja
+ Omogućuje otvaranje baze podataka pomoću podataka za prijavu
+ Otključavanje s podacima za prijavu uređaja
+ Podaci za prijavu uređaja
+ Dodirni za brisanje ključeva naprednog otključavanja
+ Napredno otključavanje baze podataka
+ Vremensko ograničenje neprednog otključavanja
+ Trajanje korištenja naprednog otključavanja prije brisanja sadržaja
+ Istek naprednog otključavanja
+ Nemoj spremati šifrirani sadržaj za napredno otključavanje
+ Sadržaj
+ Privremeno napredno otključavanje
+ Argon2id
+ Argon2d
+ Nije moguće ispravno obnoviti popis.
+ URI baze podataka nije moguće dobiti.
+ Umetnuti prijedlozi
+ Prijedlozi za automatsko popunjavanje su dodani.
+ Pokušaj prikazivanja prijedloga za automatsko popunjavanje izravno s kompatibilne tipkovnice
+ Pristup datoteci koju je opozvao upravljač datoteka. Zatvori bazu podataka i ponovo je otvori s njezinog mjesta.
+ Prepiši vanjske promjene spremanjem baze podataka ili je ponovo učitaj s najnovijim promjenama.
+ Podaci u datoteci tvoje baze podataka izmijenjeni su izvan programa.
+ Ponovo učitaj bazu podataka
\ No newline at end of file
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 005cb1736..ef9c88588 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -182,7 +182,7 @@
Az összes adathoz használt adatbázis-titkosítási algoritmus.
A kulcs előállításához a titkosítási algoritmushoz, a mesterkulcs átalakításra került egy véletlenszerűen sózott kulcselőállítási függvénnyel.
Memóriahasználat
- A kulcselőállítási függvényhez használt memóriamennyiség (bájtban).
+ A kulcselőállítási függvényhez használt memóriamennyiség.
Párhuzamosság
A kulcselőállítási függvény párhuzamosságának mértéke (azaz a szálak száma).
Rendezés
@@ -280,8 +280,6 @@
\nA csoportok (~mappák) rendszerezik a bejegyzéseket az adatbázisban.
Keresés a bejegyzések között
Adja meg a címet, felhasználónevet vagy más mezők tartalmát, hogy lekérje a jelszavát.
- Adatbázis feloldása ujjlenyomattal
- Kösse össze a jelszavát a mentett ujjlenyomatával, hogy gyorsan fel tudja oldani az adatbázist.
Bejegyzés szerkesztése
Erős jelszó létrehozása
Állítson elő egy erős jelszót, és rendelje hozzá a bejegyzéshez, adja meg egyszerűen az űrlap feltételeinek megfelelően, és ne felejtse el biztonságosan tárolni.
@@ -319,7 +317,6 @@
Támogatás
ChaCha20
AES
- Argon2
Alkalmazástéma
Az alkalmazásban használt téma
Ikoncsomag
@@ -401,7 +398,7 @@
Megújítás kényszerítése
A mesterkulcs módosításának javaslata (napban)
Javasolt megújítás
- Korlátozza az előzmények méretét bejegyzésenként (bájtban)
+ Korlátozza az előzmények méretét bejegyzésenként
Maximális méret
Korlátozza az előzmények számát bejegyzésenként
Maximális szám
@@ -467,7 +464,6 @@
Eltávolítja azokat a mellékleteket, melyek az adatbázisban szerepelnek, de nem tartoznak bejegyzéshez
Nem összekapcsolt adatok eltávolítása
Adatok
- A titkosítási objektum nem kérhető le.
Biometrikus biztonsági frissítés szükséges.
Nincs biometrikus vagy eszközazonosító beállítva.
A kulcsfájl tartalmának sosem szabad megváltoznia, és a legjobb esetben véletlenszerűen előállított adatokat kellene tartalmaznia.
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 796761e9e..9f5b5db4d 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -27,11 +27,11 @@
Aggiungi gruppo
Algoritmo di cifratura
Scadenza app
- Tempo di inattività prima del blocco dell\'app
+ Tempo di inattività prima del blocco del database
App
Impostazioni app
Parentesi
- È necessario un file manager che accetti gli Intent ACTION_CREATE_DOCUMENT e ACTION_OPEN_DOCUMENT per creare, aprire e salvare i file di database.
+ Un file manager che accetta intent action ACTION_CREATE_DOCUMENT e ACTION_OPEN_DOCUMENT è necessario creare, aprire e salvare i file del database.
Appunti eliminati
Errore negli appunti
Alcuni dispositivi non permettono alle app di usare gli appunti.
@@ -39,8 +39,8 @@
Scadenza appunti
Tempo prima di eliminare gli appunti (se supportato dal dispositivo)
Copia %1$s negli appunti
- Creazione file chiave database…
- Banca dati
+ Recupero chiave database…
+ Database
Decodifica contenuto database…
Usa come database predefinito
Numeri
@@ -64,15 +64,15 @@
La codifica a flusso Arcfour non è supportata.
KeePassDX non può gestire questo URI.
Impossibile creare il file
- Lettura del database fallita.
+ Impossibile leggere il database.
Assicurati che il percorso sia corretto.
Inserisci un nome.
Memoria insufficiente per caricare l\'intero database.
Deve essere selezionato almeno un tipo di generazione password.
Le password non corrispondono.
- \"Livello\" troppo alto. Impostato a 2147483648.
+ «Livello» troppo alto. Impostato a 2147483648.
Ogni stringa deve avere un nome.
- Inserisci un numero naturale positivo nel campo \"lunghezza\".
+ Inserisci un numero intero positivo nel campo \"Lunghezza\".
Nome campo
Valore campo
File non trovato. Prova a riaprirlo dal tuo gestore di file.
@@ -87,12 +87,12 @@
Password
Non è possibile leggere le credenziali.
Algoritmo errato.
- Formato database non riconosciuto.
+ Formato del database non riconosciuto.
Il file chiave è vuoto.
Lunghezza
Dimensione elenco elementi
Dimensione del testo nell\'elenco del gruppo
- Caricamento database…
+ Caricamento del database…
Minuscole
Nascondi le password
Maschera le password (***) in modo predefinito
@@ -114,34 +114,34 @@
Nessun risultato di ricerca
Installa un browser web per aprire questo URL.
Non cercare nelle voci di backup
- Ometti i gruppi \"Backup\" e \"Cestino\" dai risultati di ricerca
- Creazione nuovo database…
+ Ometti i gruppi «Backup» e «Cestino» dai risultati di ricerca
+ Creazione di un nuovo database…
In corso…
Protezione
Sola lettura
- KeePassDX richiede l\'autorizzazione di scrittura per poter modificare il tuo database.
+ A seconda del tuo file manager, KeepassDX potrebbe non riuscire a scrivere sulla memoria.
Elimina
Root
Livello cifratura
Livelli di cifratura aggiuntivi forniscono una maggiore protezione contro attacchi di tipo forza bruta, ma può rallentare il caricamento e il salvataggio.
- Salvataggio database…
+ Salvataggio del database…
Spazio
Cerca
Ordine naturale
Speciali
- Titolo/descrizione voce
- Risultati ricerca
+ Cerca
+ Risultati della ricerca
Trattino basso
- Versione database non supportata.
+ Versione del database non supportata.
Maiuscole
Attenzione
- Evita password con caratteri al di fuori del formato di codifica del testo nel file di database (i caratteri non riconosciuti vengono convertiti nella stessa lettera).
+ 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).
Versione %1$s
Password criptata salvata
- Questo database non ha ancora alcuna password.
- Inserisci la password e/o il file chiave per sbloccare il database.
+ Questo database non contiene alcuna credenziale.
+ Inserisci la password o il file chiave per sbloccare la base di dati.
\n
-\nEseguire il backup del file di database in un luogo sicuro dopo ogni modifica.
+\nEseguire il backup del file del database in un luogo sicuro dopo ogni modifica.
- 5 secondi
- 10 secondi
@@ -176,7 +176,7 @@
Algoritmo di cifratura del database usato per tutti i dati.
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).
Utilizzo di memoria
- Quantità di memoria (in byte) utilizzabili dalla funzione di derivazione della chiave.
+ Quantità di memoria utilizzabili dalla funzione di derivazione della chiave.
Parallelismo
Grado di parallelismo (cioè numero di thread) usato dalla funzione di derivazione della chiave.
Ordina
@@ -188,7 +188,7 @@
Creazione
Modifica
Accesso
- Continuare senza aver impostato una password di sblocco \?
+ Continuare senza aver impostato una password di sblocco\?
Continuare senza una chiave di cifratura\?
Cronologia
Aspetto
@@ -197,7 +197,7 @@
Autocompletamento di KeePassDX
Accedi con KeePassDX
Imposta servizio predefinito di autocompletamento
- Attiva l\'autocompletamento per compilare velocemente i moduli in altre app
+ Attiva l\'autocompletamento per riempire velocemente i campi in altre app
Dimensione password generata
Imposta la dimensione predefinita delle password generate
Caratteri password
@@ -212,32 +212,32 @@
Scansione di impronte
Consente la scansione biometrica per aprire il database
Elimina chiavi di cifratura
- Elimina tutte le chiavi di cifratura relative al riconoscimento dell\'impronta
+ Elimina tutte le chiavi di cifratura relative allo sblocco avanzato
Impossibile avviare questa funzione.
- La tua versione di Android %1$s non è la minima %2$s richiesta.
+ Il dispositivo usa Android %1$s, ma richiede %2$s o versioni successive.
L\'hardware relativo non è stato trovato.
Nome file
Percorso
Assegna una chiave master
Crea un nuovo database
Uso del Cestino
- Sposta i gruppi e le voci nel gruppo \"Cestino\" prima di eliminarlo
+ Sposta i gruppi e le voci nel gruppo «Cestino» prima di eliminarlo
Carattere campi
Cambia il carattere usato nei campi per una migliore visibilità
Fiducia appunti
- Permetti la copia della password e dei campi protetti negli appunti
+ Consenti la copia della password e dei campi protetti negli appunti
Attenzione: gli appunti sono condivisi da tutte le app. Se vengono copiati dati sensibili, altri software possono recuperarli.
- Nome database
- Descrizione database
- Versione database
+ Nome del database
+ Descrizione del database
+ Versione del database
Testo
- App
+ Interfaccia
Altro
Tastiera
Magitastiera
- Attiva una tastiera personale che popola le tue password e i campi di identità
+ Attiva una tastiera personale che inserisce le tue password e i campi di identità
Non consentire nessuna chiave principale
- Abilita il pulsante «Apri» se le credenziali non sono selezionate
+ Permetti di toccare il pulsante \"Apri\" se non sono selezionate credenziali
Protetto da scrittura
Apri il database in sola lettura in modo predefinito
Suggerimenti educativi
@@ -245,67 +245,63 @@
Ripristina i suggerimenti educativi
Mostra di nuovo tutte le informazioni educative
Suggerimenti educativi ripristinati
- Crea il tuo file di database
+ Crea il tuo file database
Crea il tuo primo file di gestione password.
- Apri un database esistente
- Apri il tuo file database precedente dal tuo gestore di file per continuare ad usarlo.
+ Apri un database esitente
+ Apri il tuo file database usato in precedenza con il file manager per continuare ad usarlo.
Aggiungi elementi al tuo database
- Gli elementi aiutano a gestire le tue identità digitali.
-\n
-\nI gruppi (~ cartelle) organizzano gli elementi nel database.
+ Gli elementi aiutano a gestire le tue identità digitali.
+\n
+\nI gruppi (come cartelle) organizzano gli elementi nel database.
Cerca tra gli elementi
Inserisci il titolo, il nome utente o il contenuto di altri campi per recuperare le tue password.
- Sblocco del database tramite impronta digitale
- Collega la password alla tua impronta digitale per sbloccare velocemente il database.
Modifica l\'elemento
Modifica l\'elemento con campi personalizzati. I dati possono fare riferimento ad altri campi.
Crea una password robusta
Genera una password robusta da associare all\'elemento, definiscila a seconda dei criteri del modulo e non dimenticare di tenerla al sicuro.
Aggiungi campi personalizzati
- Registra un campo aggiuntivo, inserisci delle informazioni e proteggilo se necessario.
+ Registra un campo aggiuntivo, aggiungi un valore e facoltativamente proteggilo.
Sblocca il tuo database
Proteggi da scrittura il tuo database
- Cambia la modalità di apertura per la sessione.
-\n
-\n\"Sola lettura\" impedisce modifiche accidentali al database.
-\n\"Modificabile\" permette di aggiungere, eliminare o modificare tutti gli elementi.
+ Cambia la modalità di apertura per la sessione.
+\n
+\n«Sola lettura» impedisce modifiche accidentali al databae.
+\n«Modificabile» permette di aggiungere, eliminare o modificare tutti gli elementi.
Copia un campo
I campi copiati possono essere incollati ovunque.
\n
\nUsa il metodo di inserimento che preferisci.
Blocca il database
- Blocca velocemente il database, puoi impostare l\'app per bloccarlo dopo un certo periodo e quando lo schermo si spegne.
+ Blocca velocemente il database, puoi impostare l\'applicazione per bloccarsi dopo un certo periodo e quando lo schermo si spegne.
Ordine elementi
Scegli l\'ordine di elementi e gruppi.
Partecipa
Aiuta a migliorare la stabilità, la sicurezza e ad aggiungere nuove funzioni.
Diversamente da molte app di gestione password, questa è <strong>senza pubblicità</strong>, <strong>software libero (copyleft)</strong> e non raccoglie dati personali nei suoi server, non importa quale versione usi.
- Acquistando la versione pro, avrai accesso a questo <strong>tema</strong> e soprattutto aiuterai nella <strong>realizzazione di progetti della comunità.</strong>
- Questo<strong>tema</strong> è disponibile grazie alla tua generosità.
- Per mantenere la nostra libertà ed essere sempre attivi, contiamo sul tuo <strong>contributo.</strong>
-
+ Acquistando la versione pro, avrai accesso a questa <strong>stile visivo</strong> e soprattutto aiuterai nella <strong>realizzazione dei progetti della comunità.</strong>
+ Questa <strong>stile visivo</strong> è disponibile grazie alla tua generosità.
+ Al fine di mantenere la nostra libertà ed essere sempre attivi, contiamo sul tuo <strong>contributo.</strong>
Questa funzione è <strong>in sviluppo</strong> e richiede il tuo <strong>contributo</strong> per essere disponibile a breve.
Acquistando la versione <strong>pro</strong>,
<strong>Contribuendo</strong>,
- incoraggi gli sviluppatori a creare <strong>nuove funzioni</strong> e a <strong>correggere errori</strong> secondo le tue osservazioni.
+ stai incoraggiando gli sviluppatori a creare <strong>nuove funzionalità</strong> e a <strong>correggere errori</strong> in base alle tue osservazioni.
Grazie mille per il tuo contributo.
Stiamo lavorando sodo per rilasciare questa funzione a breve.
- Non dimenticare di tenere aggiornata l\'app installando nuove versioni.
+ Ricorda di tenere aggiornata l\'app installando le nuove versioni.
Scarica
Contribuisci
Rijndael (AES)
Twofish
ChaCha20
AES
- Argon2
Tema dell\'app
Tema usato nell\'app
Pacchetto icone
Pacchetto icone usato nell\'app
Modifica elemento
Caricamento del database fallito.
- Caricamento della chiave fallito. Prova a diminuire l\' \"Utilizzo memoria\" del KDF.
+ Caricamento della chiave fallito. Prova a diminuire l\'«Utilizzo memoria» del KDF.
Mostra nomi utente
Mostra i nomi utente negli elenchi
Appunti
@@ -331,7 +327,7 @@
Modalità selezione
Non terminare l\'app…
Premere \'\'Indietro\'\' per bloccare
- Bloccare il database quando l\'utente preme il pulsante Indietro nella schermata principale
+ Blocca il database quando l\'utente preme il pulsante Indietro nella schermata principale
Pulisci alla chiusura
Blocca il database quando scade la durata degli appunti o la notifica viene chiusa dopo che inizi ad usarlo
Cestino
@@ -372,8 +368,8 @@
Contatore
Cifre
Algoritmo
- Password usa e getta
- Segreto per password usa e getta (OTP) non valido.
+ Password usa e getta (OTP)
+ Segreto della password usa e getta (OTP) non valido.
Impostare almeno una credenziale.
Non puoi copiare un gruppo qui.
La chiave segreta deve essere nel formato Base32.
@@ -383,9 +379,9 @@
%1$s con le stesse credenziali univoche %2$s è già esistente.
Sto creando il database…
Impostazioni di sicurezza
- Il databse contiene identificativi univoci univerali duplicati.
+ Il database contiene Identificativi Univoci Universali (UUID) duplicati.
Non è possibile salvare il database.
- Salva il database
+ Salva database
Svuota il cestino
Esecuzione del comando…
Vuoi eliminare definitivamente i nodi selezionati\?
@@ -399,16 +395,16 @@
Il keystore non è inizializzato correttamente.
Impostazioni della chiave principale
Chiave principale
- Contribuisci
- Garantisci il permesso di scrittura per salvare i cambiamenti del database
- Nascondi link corrotti nella lista dei database recenti
- Nascondi i link di database corrotti
+ Contributi
+ Concedi il permesso di scrittura per salvare i cambiamenti del database
+ Nascondi collegamenti corrotti nella lista dei database recenti
+ Nascondi i collegamenti dei database corrotti
Mostra le posizioni dei database recenti
Mostra file recenti
- Salva la posizione dei keyfile
+ Ricorda posizione file chiave
Ricorda la posizione dei database
- Salva posizione dei database
- Per continuare, risolvi il problema generando nuovi UUIDs per i duplicati \?
+ Ricorda posizione database
+ Risolvi il problema generando nuovi UUID per i duplicati per continuare\?
Impossibile creare il file del database.
Aggiungi allegato
Scarta
@@ -416,51 +412,51 @@
Convalida
Dimensione massima
Numero massimo
- Apri automaticamente prompt biometrico
- Limita la dimensione (in byte) della cronologia per voce
+ Apri automaticamente la richiesta
+ Limita la dimensione della cronologia per voce
Limita il numero di elementi della cronologia per voce
Gruppo cestino
La compressione dei dati riduce le dimensioni del database
Compressione dati
- Proponi l\'autenticazione biometrica quando il database è configurato per usarla
+ Richiedi automaticamente lo sblocco avanzato se il database è impostato per usarlo
Utilizza lo sblocco avanzato per aprire il database più facilmente
Copia i campi di immissione utilizzando gli appunti del tuo dispositivo
Database aperto
Biometrico
Forza rinnovo
- Consigliato cambiare la chiave principale (giorni)
+ È consigliato di cambiare la chiave principale (giorni)
Rinnovo raccomandato
- I record scaduti sono nascosti
- Nascondi i record scaduti
- Imposta la gestione delle OTP (HOTP / TOTP) per generare un token richiesto per la 2FA.
- Completo!
+ Le voci scadute non sono mostrate
+ Nascondi le voci scadute
+ Imposta la gestione della password usa e getta (HOTP/TOTP) per generare un token richiesto per l\'autenticazione a due fattori.
+ Completato!
Finalizzazione…
Avanzamento %1$d%%
Inizializzazione…
Scaricamento %1$s
- Imposta OTP
- Salva il database dopo ogni azione importante (in modalità \"Modificabile\")
+ Imposta One-Time Password (OTP)
+ Salva il database dopo ogni azione importante (in modalità «Modificabile»)
Salvataggio automatico del database
Suggerisci automaticamente risultati dal dominio web o ID dell\'applicazione
Ricerca automatica
- Dopo la pressione del tasto \"Campo\" invia il tasto \"Vai\"
- Azione auto key
- Impostazioni tastiera dispositivo
+ Azione del tasto \"Vai\" dopo aver premuto un tasto \"Campo\"
+ Tasto di azione automatica
+ Impostazioni della tastiera del dispositivo
Gzip
Nessuna
Compressione
- Colore del database customizzato
- Nome utente di default
+ Colore personalizzato del database
+ Nome utente predefinito
Disabilita
Abilita
- Richiedi il cambio della master key la prossima volta (una volta)
+ Richiedi il cambio della chiave principale la prossima volta (una volta)
Forza il rinnovo la prossima volta
- Richiedi il cambio della master key (giorni)
+ Richiedi la modifica della chiave principale (giorni)
Mostra il bottone di blocco nell\'interfaccia utente
Mostra il bottone di blocco
Impostazioni dell\'autocompletamento
Accesso al file revocato dal file manager
- Ricorda la posizione dei keyfiles dei database
+ Ricorda la posizione dei file chiave
Questa etichetta esiste già.
Riavvia l\'app contenente il campo per attivare il blocco.
Blocca riempimento automatico
@@ -477,8 +473,8 @@
Aggiungi elemento
Torna automaticamente alla tastiera precedente quando si esegue l\'azione del tasto automatico
Azione tasto automatico
- Torna automaticamente alla tastiera precedente nella schermata delle credenziali del database
- Schermata credenziali database
+ Torna automaticamente alla tastiera precedente nella schermata credenziali del database
+ Schermata credenziali del database
Cambia tastiera
Carica %1$s
Carica un allegato alla voce per salvare dati esterni importanti.
@@ -494,6 +490,69 @@
Caricare questo file sostituirà quello esistente.
Un database KeePass dovrebbe contenere solo piccoli file di utilità (come i file di chiavi PGP).
\n
-\nIl database può diventare molto grande e ridurre le prestazioni con questo caricamento.
+\nIl tuo database può diventare molto grande e ridurre le prestazioni con questo caricamento.
Info credenziali
+ Visualizza l\'UUID collegato a una voce
+ Mostra UUID
+ Il salvataggio dei dati non è consentito per una base du dati aperta in sola lettura.
+ Chiedi di salvare i dati quando un modulo viene convalidato
+ Chiedi di salvare i dati
+ Prova a salvare le informazioni di ricerca quando effettui una selezione di immissione manuale
+ Salva le informazioni di ricerca
+ Chiudi il database dopo aver usato l\'autocompletamento
+ Chiudi database
+ Torna automaticamente alla tastiera precedente dopo aver bloccato il database
+ Blocca il database
+ Prova a salvare le informazioni condivise quando effettui una selezione di immissione manuale
+ Salva le informazioni condivise
+ Notifica
+ È necessario un aggiornamento della sicurezza biometrica.
+ Eliminare definitivamente tutti i nodi dal cestino\?
+ Modalità registrazione
+ Modalità salvataggio
+ Modalità ricerca
+ Il nome del campo esiste già.
+ Il salvataggio di un nuovo elemento non è consentito in un database di sola lettura
+ Nessuna credenziale biometrica o del dispositivo è registrata.
+ Collega la password alla tua autenticazione biometrica (o del dispositivo) per sbloccare velocemente il database.
+ Sblocco avanzato del database
+ Invio
+ Backspace
+ Seleziona voce
+ Torna alla tasitera precedente
+ Campi personalizzati
+ Vuoi eliminare le chiavi di cifratura relative allo sblocco avanzato\?
+ Scadenza sblocco avanzato
+ Non salvare alcun contenuto criptato per usare lo sblocco avanzato
+ Validità dello sblocco avanzato prima di eliminarne il contenuto
+ Scadenza sblocco avanzato
+ Sblocco avanzato temporaneo
+ Utilizza le credenziali del dispositivo per sbloccare il database
+ Sblocco con credenziali dispositivo
+ Tocca per eliminare le chiavi di sblocco avanzato
+ Contenuto
+ Non è possibile inizializzare lo sblocco avanzato.
+ Non è possibile riconoscere lo sblocco avanzato
+ Non è possibile leggere la chiave di sblocco avanzato. Eliminala e ripeti la prodecura dello sblocco.
+ Estrai le credenziali del database con i dati dallo sblocco avanzato
+ Attenzione: dovrai sempre ricordare la password principale anche se usi lo sblocco avanzato.
+ Riconoscimento con sblocco avanzato
+ Credenziali del dispositivo
+ Inserisci la password, quindi clicca sull\'icona \"Sblocco avanzato\".
+ Errore sblocco avanzato: %1$s
+ Apri il database con lo sblocco avanzato
+ Autentica con lo sblocco avanzato per sbloccare il database
+ Autentica con lo sblocco avanzato per salvare le credenziali
+ Elimina chiave di sblocco avanzato
+ Argon2id
+ Argon2d
+ Non è possibile ricostruire la lista correttamente.
+ Non è stato recuperato l\'indirizzo del database.
+ Suggerimento di riempimento aggiunto.
+ Mostra i suggerimenti di riempimento campi in una tastiera compatibile
+ Suggerimenti in linea
+ L\'accesso al file è stato revocato dal file manager, chiudi il database e riaprilo dalla sua posizione originale.
+ Sovrascrivi le modifiche esterne salvano il database o ricaricalo con gli ultimi cambiamenti.
+ I dati nel tuo database sono stati modificati al di fuori di questa app.
+ Ricarica database
\ No newline at end of file
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index f2cfc3a6d..c10c9157e 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -17,8 +17,7 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see .
translated by Arthur Zamarin
--->
-
+-->
משוב
דף הבית
KeePassDX היא תוכנה המממשת את מנהל הסיסמאות KeePass לאנרואיד.
@@ -27,7 +26,7 @@
הוסף קבוצה
אלגוריתם
פסק זמן ליישום
- זמן לפני נעילת מסד הנתונים כאשר היישום לא פעיל.
+ זמן לפני נעילת מסד הנתונים כאשר היישום לא פעיל
יישום
הגדרות יישום
סוגריים
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 534ccab53..035412598 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -226,7 +226,7 @@
変換ラウンド
変換ラウンドを増やすことでブルート フォース攻撃に対する保護が強化されますが、読み込みと保存が本当に遅くなる可能性があります。
メモリ使用量
- 鍵導出関数が使用するメモリの量(バイト単位)です。
+ 鍵導出関数が使用するメモリの量です。
並列処理
鍵導出関数が使用する並列処理のレベル(スレッド数)です。
データベースを保存しています…
@@ -273,7 +273,6 @@
キーストアが正しく初期化されていません。
保存された暗号化済みパスワード
データベースの保存済み認証情報はありません。
- crypto オブジェクトを取得できません。
履歴
デザイン
生体認証
@@ -305,10 +304,10 @@
高度なロック解除を使用して、データベースをより簡単に開きます
生体認証によるロック解除
生体情報をスキャンしてデータベースを開くことができるようにします
- 生体認証プロンプトを自動で開く
- データベースが生体認証を使用するように設定されている場合、生体情報の取得を自動的に求めます
+ プロンプトを自動で開く
+ データベースに設定されている場合、高度なロック解除を自動的に要求します
暗号鍵を削除
- 生体認証に関するすべての暗号鍵を削除します
+ 高度なロック解除に関するすべての暗号鍵を削除します
この機能を起動できませんでした。
デバイスは Android %1$s を実行していますが、%2$s 以降が必要です。
対応するハードウェアが見つかりませんでした。
@@ -326,14 +325,14 @@
最大値
エントリーあたりの履歴項目の数を制限します
最大サイズ
- エントリーあたりの履歴のサイズ(バイト単位)を制限します
+ エントリーあたりの履歴のサイズを制限します
更新を推奨
マスターキーの変更を推奨します(日数)
更新を強制
マスターキーの変更を必須にします(日数)
次回更新を強制
次回マスターキーの変更を必須にします(1 回のみ)
- フィールド フォント
+ フィールド書体
フィールド内で使用するフォントを変更して、文字を見やすくします
クリップボードの信頼
エントリーのパスワードと保護されたフィールドを、クリップボードにコピーすることを許可します
@@ -349,7 +348,7 @@
カスタムカラー
データベースのバージョン
テキスト
- アプリ
+ インターフェース
その他
圧縮
なし
@@ -389,7 +388,7 @@
データベース認証情報の画面
データベース認証情報の画面で、切り替え前のキーボードへ自動的に戻します
自動キーアクション
- 自動キーアクションの実行後、切り替え前のキーボードへ自動的に戻します
+ [自動キーアクション] の実行後、切り替え前のキーボードへ自動的に戻します
データベースをロック
データベースのロック後、切り替え前のキーボードへ自動的に戻します
データベースを閉じる
@@ -427,8 +426,6 @@
エントリーはデジタル ID の管理に役立ちます。\n\nグループ(≒フォルダ)はデータベース内のエントリーを整理します。
エントリーを検索
パスワードを取得するには、タイトル、ユーザー名、または他のフィールドの内容を入力します。
- 生体認証によるロック解除
- スキャンした生体情報にパスワードをリンクして、データベースのロックをすばやく解除します。
エントリーを編集
エントリーをカスタム フィールドとともに編集します。共有データは異なるエントリーのフィールド間で参照することができます。
強力なパスワードを作成
@@ -442,7 +439,10 @@
データベースのロックを解除
データベースのロックを解除するには、パスワードまたはキーファイル、またはその両方を入力します。\n\nデータベース ファイルは変更するたびに安全な場所へバックアップしてください。
データベースの書き込みを禁止
- セッションのロック解除モードを変更します。\n\n[書き込み禁止] では、データベースに対する意図しない変更を防ぐことができます。\n[変更可能] では、すべての要素を追加、削除、変更できます。
+ セッションのロック解除モードを変更します。
+\n
+\n[書き込み禁止] では、データベースに対する意図しない変更を防ぐことができます。
+\n[変更可能] では、すべての要素を必要に応じて追加、削除、変更できます。
フィールドをコピー
コピーしたフィールドはどこにでも貼り付けることができます。\n\nお好みのフォーム入力方法を使用してください。
データベースをロック
@@ -474,7 +474,6 @@
Twofish
ChaCha20
AES
- Argon2
- 5秒
- 10秒
@@ -511,4 +510,46 @@
データの保存は読み取り専用として開かれたデータベースでは許可されていません。
保存モード
検索モード
+ フィールド名はすでに存在します。
+ 高度なロック解除用の鍵を削除
+ 高度なロック解除を使って認証できませんでした
+ エンター
+ バックスペース
+ 前のキーボードに戻る
+ カスタム フィールド
+ 高度なロック解除に関する暗号鍵をすべて削除しますか?
+ デバイス認証情報を使用してデータベースを開くことができるようにします
+ デバイス認証情報によるロック解除
+ デバイス認証情報
+ パスワードを入力し、[高度なロック解除] ボタンをタップします。
+ 高度なロック解除プロンプトを初期化できません。
+ 高度なロック解除のエラー:%1$s
+ 高度なロック解除用の鍵が読み取れません。削除してロック解除の手順をやり直してください。
+ 高度なロック解除を使ってデータベース認証情報を取り出します
+ 高度なロック解除を使ってデータベースを開く
+ 警告:高度なロック解除を使用する場合も、マスター パスワードを記憶する必要があります。
+ 高度なロック解除の認証
+ ログイン プロンプトを開いて認証情報を保存する
+ ログイン プロンプトを開いてロックを解除する
+ エントリーを選択
+ スキャンした生体情報またはデバイス認証情報にパスワードをリンクして、データベースのロックをすばやく解除します。
+ データベースの高度なロック解除
+ 高度なロック解除のタイムアウト
+ コンテンツを削除して高度なロック解除を終了するまでの期間
+ 高度なロック解除の有効期限
+ 高度なロック解除に使用する暗号化コンテンツを保存しません
+ 一時的な高度なロック解除
+ タップして高度なロック解除用の鍵を削除する
+ コンテンツ
+ Argon2id
+ Argon2d
+ データベースの URI が取得できません。
+ 自動入力候補が追加されました。
+ 自動入力候補を互換性のあるキーボード上に直接表示することを試みます
+ インライン自動入力
+ ファイルへのアクセス権がファイル マネージャーによって取り消されました。データベースを閉じて、ファイルの場所から再度開いてください。
+ データベースを保存して外部の変更を上書きするか、再度読み込んで最新の変更を反映させてください。
+ データベース ファイルに含まれる情報は、アプリの外部で変更されています。
+ データベースを再度読み込む
+ リストを正しく再構築できません。
\ No newline at end of file
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index b36e9c87b..b8013d4f2 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -94,7 +94,7 @@
Rakto failas
Įrašo pavadinimas/aprašymas
Pakeisti master raktą
- Naudota:
+ Naudota
Maskuoti slaptažodį
Tarpas
Specialus
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index 8736cc9c4..05c33cb72 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -25,7 +25,7 @@
Jauna grupa
Šifrēšanas algoritms
Pielikuma taimauts
- Bloķēšanas taimauts, kad programma nav aktīva.
+ Bloķēšanas taimauts, kad programma nav aktīva
Programma
Programmas iestatījumi
Iekavas
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index ba7ea9c8d..848d93870 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -192,7 +192,6 @@
വിപുലീകരിച്ച ASCII
ആപ്പിൽ ഉപയോഗിച്ചിരിക്കുന്ന ഐക്കൺ പാക്ക്
അപ്പ്ലിക്കേഷന്റെ തീം
- Argon2
അന്തിമമാക്കുന്നു. . .
നിങ്ങളുടെ സംഭാവനയ്ക്ക് ഒരുപാട് നന്ദി.
ഡാറ്റാബേസ് തുറക്കുക
@@ -301,7 +300,7 @@
മുമ്പുള്ള ഗ്രൂപ്പുകൾ
ആദ്യത്തേത് ഏറ്റവും കുറഞ്ഞത്↓
കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന സമാന്തരതയുടെ ബിരുദം (അതായത് ത്രെഡുകളുടെ എണ്ണം).
- കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന മെമ്മറിയുടെ അളവ് (ബൈറ്റുകളിൽ).
+ കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന മെമ്മറിയുടെ അളവ് .
എല്ലാ ഡാറ്റയ്ക്കും ഉപയോഗിക്കുന്ന ഡാറ്റാബേസ് എൻക്രിപ്ഷൻ അൽഗോരിതം.
സമീപകാല ഡാറ്റാബേസുകളുടെ പട്ടികയിൽ നിന്ന് തകർന്ന ലിങ്കുകൾ മറയ്ക്കുക
തകർന്ന ഡാറ്റാബേസ് ലിങ്കുകൾ മറയ്ക്കുക
@@ -369,7 +368,6 @@
അറിയിപ്പ് വിവരം
എൻട്രികളും ഗ്രൂപ്പുകളും എങ്ങനെ അടുക്കുന്നുവെന്ന് തിരഞ്ഞെടുക്കുക.
ഇനം തരംതിരിക്കൽ
- ബയോമെട്രിക് ഡാറ്റാബേസ് അൺലോക്കുചെയ്യൽ
ടൈം ഔട്ട്
ഫീൽഡിന്റെ ഫോണ്ട്
ഡാറ്റ
@@ -390,4 +388,12 @@
രജിസ്ട്രേഷൻ മോഡ്
തിരയൽ മോഡ്
read-only ഡാറ്റാബേസിൽ പുതിയ ഒരു ഇനം സംരക്ഷിക്കാൻ കഴിയില്ല
+ വിപുലമായ ഡാറ്റാബേസ് അൺലോക്ക്
+ എൻട്രി തിരഞ്ഞെടുക്കുക
+ ഇഷ്ടാനുസൃത ഫീൽഡുകൾ
+ കീകൾ
+ അറിയിപ്പ്
+ പുതുക്കൽ ശുപാർശ ചെയ്യുക
+ ഉള്ളടക്കം
+ ഉപകരണ ക്രെഡൻഷ്യൽ
\ No newline at end of file
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 18f562846..d9a4a2507 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -145,7 +145,7 @@
Krypteringsomganger
Flere krypteringsomganger gir ytterligere beskyttelse mot råmaktsangrep, men kan virkelig sakke ned innlasting og lagring.
Minnebruk
- Mengden minne (i byte) brukt til nøkkelutledelsesfunksjonen.
+ Mengden minne brukt til nøkkelutledelsesfunksjonen.
Parallellitet
Graden av parallellitet (dvs. antallet tråder) brukt av nøkkelutledingsfunksjonen.
Lagrer database…
@@ -240,8 +240,6 @@
\nLegg til grupper (tilsvarende mapper) for å organisere dine oppføringer og databasen din.
Søk i dine oppføringer
Søk etter oppføringer etter tittel, brukernavn eller andre felter for å hente passordene dine.
- Lås opp databasen din med fingeravtrykket ditt
- Lenk passordet og fingeravtrykket ditt for å låse opp databasen din enkelt.
Rediger oppføringen
Rediger din oppføring med egendefinerte felter, referanser til pooldata kan legges til mellom felter av forskjellige oppføringer.
Opprett et sterkt passord
@@ -286,7 +284,6 @@
Twofish
ChaCha20
AES
- Argon2
Velg en drakt
Tilpass programdrakten ved å endre fargene
Velg en ikonpakke
@@ -419,4 +416,13 @@
Innvilg skrivetilgang for å lagre databaseendringer
Skjul ødelagte lenker i listen over nylige databaser
Skjul ødelagte databaselenker
+ Spør om lagring av data
+ Avansert opplåsningsfeil: %1$s
+ Det anbefales ikke å legge til en tom nøkkelfil.
+ Legg til filen uansett\?
+ Registreringsmodus
+ Lagringsmodus
+ Søkemodus
+ Feltnavnet finnes allerede.
+ Legg til element
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index c4540487a..05ae02d30 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -34,7 +34,7 @@
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.
Klembord gewist
Klembordtime-out
- Tijd van opslag op het klembord (indien ondersteund op jouw apparaat)
+ Tijd van opslag op het klembord (voor zover ondersteund op dit apparaat)
Selecteer om %1$s naar klembord te kopiëren
Databasesleutel ophalen…
Database
@@ -67,7 +67,7 @@
Onvoldoende vrij geheugen om de gehele database te laden.
Je moet minimaal één soort wachtwoordgenerering kiezen.
De wachtwoorden komen niet overeen.
- \"Cycli-waarde\" te groot. Wordt ingesteld op 2147483648.
+ \"Cycli-waarde\" te groot. Deze wordt ingesteld op 2147483648.
Voer een positief geheel getal in in het veld \"Lengte\".
Bestandsbeheer
Wachtwoord genereren
@@ -81,16 +81,16 @@
Kan referenties niet lezen.
Databaseformaat kan niet worden herkend.
Lengte
- Lengte van lijst met items
- Tekstgrootte in de lijst
+ Lijstgrootte
+ Tekstgrootte in de itemslijst
Database laden…
Kleine letters
Wachtwoorden verbergen
- Wachtwoorden standaard verbergen (***)
+ Wachtwoorden standaard maskeren (***)
Over
Hoofdsleutel wijzigen
Instellingen
- Instellingen database
+ Database-instellingen
Verwijderen
Doneren
Bewerken
@@ -180,7 +180,7 @@
Database-encryptie-algoritme dat voor alle gegevens wordt gebruikt.
Om de sleutel voor het algoritme te kunnen genereren, wordt de hoofdsleutel getransformeerd middels een willekeurige afleidingsfunctie.
Geheugengebruik
- De hoeveelheid geheugen (bytes) dat de afleidingsfunctie mag gebruiken.
+ De hoeveelheid geheugen dat de afleidingsfunctie mag gebruiken.
Parallellen
Het aantal parallellen (aantal threads) dat de afleidingsfunctie mag gebruiken.
Sorteren
@@ -205,15 +205,15 @@
Auto-aanvullen
KeePassDX auto-aanvullendienst
Inloggen met KeePassDX
- Dienst voor automatisch aanvullen
+ Dienst automatisch aanvullen
Schakel de dienst in om formulieren in andere apps in te vullen
Gegenereerde wachtwoordlengte
- Standaardlengte van gegenereerd wachtwoord instellen
+ Stel de standaardlengte van gegenereerd wachtwoord in
Wachtwoordtekens
Toegestane wachtwoordtekens instellen
Klembord
Klembordmeldingen
- Toon klembordmeldingen om velden te kopiëren bij het bekijken van een item
+ Schakel klembordmeldingen in om velden te kopiëren bij het bekijken van een item
Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.
Vergrendelen
Schermvergrendeling
@@ -222,9 +222,9 @@
Ontgrendelen met biometrie
Gebruik biometrische herkenning om de database te openen
Coderingssleutels verwijderen
- Alle sleutels voor biometrische herkenning verwijderen
+ Alle coderingssleutels met betrekking tot geavanceerde ontgrendelingsherkenning verwijderen
Kan deze functie niet starten.
- Het apparaat draait op Android %1$s, maar %2$s of hoger is vereist.
+ Dit apparaat draait op Android %1$s, maar %2$s of hoger is vereist.
De bijbehorende hardware werd niet gevonden.
Bestandsnaam
Pad
@@ -241,19 +241,19 @@
Databaseomschrijving
Databaseversie
Tekst
- App
+ Gebruikersomgeving
Overig
Toetsenbord
Magikeyboard
- Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren
+ Activeer een aangepast toetsenbord dat je wachtwoorden en identiteitsvelden vult
Geen hoofdwachtwoord toestaan
- Maakt het mogelijk op de knop \"Openen\" te tikken als er geen inloggegevens zijn geselecteerd
+ Schakel de knop \"Openen\" in als er geen referenties zijn geselecteerd
Alleen-lezen
Open de database standaard alleen-lezen
Informatieve tips
Markeer elementen om te leren hoe de app werkt
Informatieve tips opnieuw instellen
- Informatieve tips opnieuw weergeven
+ Informatieve tips opnieuw tonen
Informatieve tips opnieuw ingesteld
Maak je databasebestand aan
Maak je eerste wachtwoordbeheerbestand aan.
@@ -265,8 +265,6 @@
\nGroepen (~mappen) organiseren de items in je database.
Doorzoek al je items
Doorzoek items op titel, gebruikersnaam of andere velden om wachtwoorden te vinden.
- Biometrische database -ontgrendeling
- Koppel je wachtwoord aan je biometrie om de database snel te ontgrendelen.
Item bewerken
Bewerk het item met aangepaste velden. Referenties kunnen worden toegevoegd tussen velden van verschillende items.
Genereer een sterk wachtwoord
@@ -275,10 +273,10 @@
Registreer een extra veld, voeg een waarde toe en bescherm dit desgewenst.
Ontgrendel je database
Database alleen-lezen
- 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.
+ 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.
Veld kopiëren toestaan
Kopieer een veld en plak de inhoud waar je maar wilt.
\n
@@ -299,16 +297,15 @@
motiveer je ontwikkelaars om <strong>nieuwe functies</strong> te creëren en <strong>problemen op te lossen</strong>.
Hartelijk bedankt voor je bijdrage.
We zijn druk bezig om deze functie snel beschikbaar te stellen.
- Vergeet niet om je app up-to-date te houden door nieuwe versies te installeren.
+ Vergeet niet je app up-to-date te houden door nieuwe versies te installeren.
Downloaden
Bijdragen
ChaCha20
AES
- Argon2
App-thema
Thema gebruikt in de app
- Pictogrammenverzameling
- Pictogrammenverzameling in gebruik
+ Icon pack
+ Gebruikt Icon Pack
Build %1$s
Magikeyboard
Magikeyboard (KeePassDX)
@@ -331,14 +328,14 @@
Selectiemodus
App niet afsluiten…
Druk \'Terug\' om te vergrendelen
- Vergrendel de database wanneer de gebruiker op de knop Terug in het hoofdscherm klikt
+ Vergrendel de database wanneer de gebruiker in het hoofdscherm op de knop Terug klikt
Wissen bij afsluiten
Vergrendel de database wanneer de duur van het klembord verloopt of de melding wordt gesloten nadat u deze bent gaan gebruiken
Prullenmand
Itemselectie
Invoervelden in Magikeyboard tonen bij het bekijken van een item
Wachtwoord wissen
- Wis het ingevoerde wachtwoord na een poging met een database te verbinden
+ Wis het ingevoerde wachtwoord na een verbindingspoging met een database
Bestand openen
Onderliggende items
Knooppunt toevoegen
@@ -356,7 +353,7 @@
UUID
Je kan hier geen item plaatsen.
Je kan hier geen item kopiëren.
- Toon het aantal items
+ Aantal items tonen
Toon het aantal items in een groep
Achtergrond
Update
@@ -364,8 +361,8 @@
Kan geen database aanmaken met dit wachtwoord en sleutelbestand.
Geavanceerd ontgrendelen
Biometrie
- Automatisch om biometrie vragen
- Automatisch om biometrie vragen als een database hiervoor is ingesteld
+ Auto-open suggestie
+ Automatisch om geavanceerde ontgrendeling vragen als een database hiervoor is ingesteld
Inschakelen
Uitschakelen
Hoofdsleutel
@@ -393,14 +390,14 @@
De database bevat dubbele UUID\'s.
Probleem oplossen door nieuwe UUID\'s te genereren voor de duplicaten\?
Database geopend
- Kopieer invoervelden met behulp van het klembord van uw apparaat
+ Kopieer invoervelden met behulp van het klembord van dit apparaat
Geavanceerde ontgrendeling gebruiken om een database gemakkelijker te openen
Gegevenscompressie
Gegevenscompressie verkleint de omvang van de database
Maximum aantal
Beperk het aantal geschiedenisitems per item
Maximum
- Geschiedenis (bytes) per item beperken
+ Geschiedenis per item beperken
Vernieuwing aanbevelen
Aanbeveling de hoofdsleutel te wijzigen (dagen)
Vernieuwing afdwingen
@@ -416,20 +413,20 @@
Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)
OTP instellen
Locatie van sleutelbestanden opslaan
- Databaselocaties onthouden
+ Databaselocaties opslaan
Verlopen items worden niet getoond
- Verberg verlopen items
- Klaar!
+ Verlopen items verbergen
+ Voltooid!
Voltooien…
Voortgang: %1$d%%
Initialiseren…
Download %1$s
- Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat wordt gevraagd voor tweefactorauthenticatie (2FA).
+ Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren voor tweefactorauthenticatie (2FA).
Automatisch opslaan
Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID
Automatisch zoeken
Prullenbak
- Geeft de vergrendelknop weer in de gebruikersinterface
+ Geef de vergrendelknop weer in de gebruikersinterface
Vergrendelknop weergeven
Instellingen voor automatisch aanvullen
De sleutelopslag is niet correct geïnitialiseerd.
@@ -437,10 +434,10 @@
Toegang tot het bestand ingetrokken door bestandsbeheer
Bestandstoegang verlenen om databasewijzigingen op te slaan
Opdracht uitvoeren…
- Verberg gebroken links in de lijst met recente databases
- Verberg corrupte databasekoppelingen
- Onthoudt waar de databasesleutelbestanden zijn opgeslagen
- Onthoudt waar de databases zijn opgeslagen
+ Gebroken links in de lijst met recente databases verbergen
+ Corrupte databasekoppelingen verbergen
+ Onthoud de locatie van databasesleutelbestanden
+ Onthoud de locatie van databases
Zoekopdracht aanmaken bij het openen van een database
Snel zoeken
Geschiedenis wissen
@@ -473,19 +470,19 @@
Item toevoegen
\"Gaan\"-toetsactie na het indrukken van een \"Veld\"-toets
Automatische toetsactie
- Schakel automatisch terug naar het vorige toetsenbord na het uitvoeren van de Automatische toetsactie
+ Schakel automatisch terug naar het vorige toetsenbord na het uitvoeren van de \"Automatische toetsactie\"
Automatische toetsactie
Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm
Scherm Databasereferenties
Van toetsenbord wisselen
- %1$s uploaden
- Upload een bijlage bij dit item om belangrijke externe gegevens op te slaan.
+ Upload %1$s
+ Voeg een bijlage toe aan dit item om belangrijke externe gegevens op te slaan.
Bijlage toevoegen
- Toch het bestand toevoegen\?
- Een KeePass-database mag alleen kleine hulpprogramma-bestanden bevatten (zoals PGP-sleutelbestanden).
+ Het bestand toch toevoegen\?
+ 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.
- Als je dit bestand uploadt, wordt het bestaande vervangen.
+\nMet deze upload kan de database erg groot worden en kunnen de prestaties verminderen.
+ Uploaden van dit bestand zal het bestaande bestand vervangen.
Inloggegevens
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.
Deze gegevens toch verwijderen\?
@@ -494,4 +491,56 @@
Gegevens
Niet-gekoppelde gegevens verwijderen
Verwijdert bijlagen die in de database staan, maar niet aan een item zijn gekoppeld
+ Geeft de UUID weer die aan een item is gekoppeld
+ UUID tonen
+ Het opslaan van gegevens is niet toegestaan voor een database die is geopend als alleen-lezen.
+ Vraag om gegevens op te slaan wanneer een formulier is gevalideerd
+ Vragen om gegevens op te slaan
+ Probeer zoekinformatie op te slaan bij u een handmatige invoerselectie
+ Zoekinformatie opslaan
+ Sluit de database na een selectie voor automatisch aanvullen
+ Database sluiten
+ Schakel na het vergrendelen van de database automatisch terug naar het vorige toetsenbord
+ Database vergrendelen
+ Probeer gedeelde informatie op te slaan bij een handmatige invoerselectie
+ Gedeelde info opslaan
+ Melding
+ Biometrische beveiligingsupdate vereist.
+ Geen biometrische gegevens of apparaatgegevens geregistreerd.
+ Alles definitief uit de prullenbak verwijderen\?
+ Registratiemodus
+ Veilige modus
+ Zoekmodus
+ Het opslaan van een nieuw item is niet toegestaan in een alleen-lezen database
+ Koppel je wachtwoord aan je gescande biometrische gegevens of apparaatreferentie om je database snel te ontgrendelen.
+ Geavanceerde database-ontgrendeling
+ Enter
+ Backspace
+ Item selecteren
+ Terug naar vorig toetsenbord
+ Aangepaste velden
+ Alle coderingssleutels met betrekking tot geavanceerde ontgrendelingsherkenning verwijderen\?
+ Time-out voor geavanceerd ontgrendelen
+ Duur van geavanceerd ontgrendelingsgebruik voordat de inhoud wordt verwijderd
+ Vervaltijd voor geavanceerde ontgrendeling
+ Sla geen versleutelde inhoud op om geavanceerde ontgrendeling te gebruiken
+ Tijdelijke geavanceerde ontgrendeling
+ Hiermee kan je de referentie van je apparaat gebruiken om de database te openen
+ Ontgrendeling met apparaatreferenties
+ Tik om geavanceerde ontgrendelingstoetsen te verwijderen
+ Inhoud
+ Apparaatreferentie
+ Typ het wachtwoord en klik vervolgens op de knop \"Geavanceerd ontgrendelen\".
+ Kan geavanceerde ontgrendelingsprompt niet initialiseren.
+ Geavanceerde ontgrendelingsfout: %1$s
+ Kan geavanceerde ontgrendelingsafdruk niet herkennen
+ Kan de geavanceerde ontgrendelingssleutel niet lezen. Verwijder deze en herhaal de herkenningsprocedure voor het ontgrendelen.
+ Databasegegevens uitpakken met geavanceerde ontgrendelingsgegevens
+ Open database met geavanceerde ontgrendelingsherkenning
+ Waarschuwing: je moet nog steeds je hoofdwachtwoord onthouden als je geavanceerde ontgrendelingsherkenning gebruikt.
+ Geavanceerde ontgrendelingsherkenning
+ Open de geavanceerde ontgrendelingsprompt om inloggegevens op te slaan
+ Open de geavanceerde ontgrendelingsprompt om de database te ontgrendelen
+ Geavanceerde ontgrendelingssleutel verwijderen
+ De veldnaam bestaat al.
\ No newline at end of file
diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml
index 19cc5d963..bd9511b38 100644
--- a/app/src/main/res/values-nn/strings.xml
+++ b/app/src/main/res/values-nn/strings.xml
@@ -25,7 +25,7 @@
Legg til gruppe
Encryption algorithm
Programtidsavbrot
- Idle time before locking the database
+
App
Programinnstillingar
Parentesar
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index 753acdf16..6c9197cfe 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -338,8 +338,6 @@
ਡਾਟਾਬੇਸ ਬੰਦ ਕਰੋ
ਲਿਖਣ ਤੋਂ ਸੁਰੱਖਿਅਤ
ਐਂਟਰੀਆਂ ਵਿੱਚੋਂ ਲੱਭੋ
- ਬਾਇਓਮੈਟਰਿਕ ਡਾਟਾਬੇਸ ਅਣ-ਲਾਕ ਕਰੋ
- ਤੁਹਾਡੇ ਡਾਟਾਬੇਸ ਨੂੰ ਫ਼ੌਰੀ ਅਣ-ਲਾਕ ਕਰਨ ਲਈ ਆਪਣੇ ਪਾਸਵਰਡ ਨੂੰ ਆਪਣੇ ਸਕੈਨ ਕੀਤੇ ਬਾਇਮੈਟਰਿਕ ਨਾਲ ਲਿੰਕ ਕਰੋ।
ਐਂਟਰੀ ਨੂੰ ਸੋਧੋ
ਅਟੈਚਮੈਂਟ ਜੋੜੋ
ਖ਼ਾਸ ਬਾਹਰੀ ਡਾਟਾ ਸੰਭਾਲਣ ਲਈ ਤੁਹਾਡੀ ਐਂਟਰੀ ਲਈ ਅਟੈਚਮੈਂਟ ਅੱਪਲੋਡ ਕਰੋ।
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index f9b10932b..309e9c809 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -175,7 +175,7 @@
Algorytm szyfrowania bazy danych używany dla wszystkich danych.
Aby wygenerować klucz dla algorytmu szyfrowania, klucz główny jest transformowany przy użyciu losowo solonej funkcji wyprowadzania klucza.
Użycie pamięci
- Ilość pamięci (w bajtach) do użycia przez funkcję wyprowadzania klucza.
+ Ilość pamięci do użycia przez funkcję wyprowadzania klucza.
Równoległy
Stopień równoległości (tj. Liczba wątków) wykorzystywany przez funkcję wyprowadzania klucza.
Sortuj
@@ -218,7 +218,7 @@
Skanowanie odcisków palców
Umożliwia zeskanowanie danych biometrycznych w celu otwarcia bazy danych
Usuń klucze szyfrowania
- Usuń wszystkie klucze szyfrowania związane z rozpoznawaniem linii papilarnych
+ Usuń wszystkie klucze szyfrujące związane z zaawansowanym rozpoznawaniem odblokowania
Nie można uruchomić tej funkcji.
Urządzenie pracuje na systemie Android %1$s, ale wymaga wersji %2$s lub nowszej.
Nie można znaleźć odpowiedniego sprzętu.
@@ -228,7 +228,7 @@
Utwórz nową bazę danych
Wykorzystaj kosz
Przenosi grupy i wpisy do grupy \"Kosz\" przed usunięciem
- Pole czcionka
+ Czcionka pola
Zmień czcionkę użytą w polach, aby poprawić widoczność postaci
Zaufanie do schowka
Zezwalanie na kopiowanie hasła wejściowego i chronionych pól do schowka
@@ -237,7 +237,7 @@
Opis bazy danych
Wersja bazy danych
Tekst
- Aplikacja
+ Wygląd
Inne
Klawiatura
Magikeyboard
@@ -261,8 +261,6 @@
\nGrupy (~ foldery) organizują wpisy w bazie danych.
Przeszukuj wpisy
Wprowadź tytuł, nazwę użytkownika lub zawartość innych pól, aby odzyskać swoje hasła.
- Biometryczne odblokowanie bazy danych
- Połącz swoje hasło z zeskanowanym odciskiem palca, aby szybko odblokować bazę danych.
Edytuj wpis
Edytuj swój wpis za pomocą pól niestandardowych. Dane puli mogą być przywoływane między różnymi polami wprowadzania.
Utwórz silne hasło
@@ -273,8 +271,8 @@
Zapisz ochronę swojej bazy danych
Zmień tryb otwierania sesji.
\n
-\n„Zabezpieczony przed zapisem” zapobiega niezamierzonym zmianom w bazie danych.
-\n„Modyfikowalne” pozwala dodawać, usuwać lub modyfikować wszystkie elementy.
+\n\"Ochrona przed zapisem\" zapobiega niezamierzonych zmian w bazie danych.
+\n\"Modyfikowalne\" pozwala dodawać, usuwać lub modyfikować wszystkie elementy, jak chcesz.
Skopiuj pole
Skopiowane pola można wkleić w dowolnym miejscu.
\n
@@ -295,12 +293,11 @@
zachęcasz programistów do tworzenia <strong>nowych funkcji</strong> i <strong>naprawy błędów</strong> zgodnie z Twoimi uwagami.
Wielkie dzięki za twój wkład.
Ciężko pracujemy, aby szybko udostępnić tę funkcję.
- Nie zapomnij o aktualizacji aplikacji, instalując nowe wersje.
+ Pamiętaj, aby aktualizować swoją aplikację, instalując nowe wersje.
Pobieranie
Przyczyń się
ChaCha20
AES
- Argon2
Motyw aplikacji
Motyw używany w aplikacji
Pakiet ikon
@@ -359,10 +356,10 @@
Nie można utworzyć bazy danych przy użyciu tego hasła i pliku klucza.
Zaawansowane odblokowywanie
Biometryczne
- Automatyczne otwieranie monitu biometrycznego
+ Automatyczne otwieranie
Włącz
Wyłącz
- Automatycznie pytaj o dane biometryczne, jeśli baza danych jest ustawiona do korzystania z nich
+ Automatyczne żądanie zaawansowanego odblokowania, jeśli baza danych jest skonfigurowana do korzystania z niej
Węzły podrzędne
Klucz główny
Zabezpieczenia
@@ -375,7 +372,7 @@
Cyfry
Algorytm
OTP
- Nie można tutaj skopiować wpisu.
+ Tutaj nie można skopiować grupy.
Tworzenie bazy danych…
Ustawienia zabezpieczeń
Ustawienia klucza głównego
@@ -405,7 +402,7 @@
Użyj zaawansowanego odblokowywania w celu łatwiejszego otwierania bazy danych
Kompresja danych zmniejsza rozmiar bazy danych
Maksymalna liczba
- Ogranicz rozmiar historii (w bajtach) na wpis
+ Ogranicz rozmiar historii na wpis
Wymuś odnowienie
Wymuś odnowienie następnym razem
Wymagaj zmiany klucza głównego następnym razem (raz)
@@ -506,7 +503,6 @@
Spróbuj zapisać udostępnione informacje podczas ręcznego wybierania pozycji
Zapisz udostępnione informacje
Powiadomienia
- Nie można pobrać obiektu kryptograficznego.
Wymagana aktualizacja zabezpieczeń biometrycznych.
Nie zarejestrowano żadnych danych biometrycznych ani danych urządzenia.
Trwale usunąć wszystkie węzły z kosza\?
@@ -514,4 +510,46 @@
Tryb zapisywania
Tryb wyszukiwania
Zapisywanie nowego elementu nie jest dozwolone w bazie danych tylko do odczytu
+ Nazwa pola już istnieje.
+ Enter
+ Backspace
+ Wybierz pozycję
+ Wróć do poprzedniej klawiatury
+ Pola niestandardowe
+ Czy usunąć wszystkie klucze szyfrowania związane z zaawansowanym rozpoznawaniem odblokowania\?
+ Umożliwia otwieranie bazy danych za pomocą poświadczeń urządzenia
+ Odblokowanie uwierzytelniające urządzenia
+ Uwierzytelnienie urządzenia
+ Wpisz hasło, a następnie kliknij przycisk \"Zaawansowane odblokowanie\".
+ Nie można rozpoznać zaawansowanego wydruku odblokowującego
+ Nie można odczytać zaawansowanego klucza odblokowuj. Usuń go i powtórz procedurę rozpoznawania odblokowania.
+ Wyodrębnij poświadczenia bazy danych z zaawansowanymi danymi odblokowującymi
+ Ostrzeżenie: Jeśli używasz zaawansowanego rozpoznawania odblokowania, nadal musisz zapamiętać hasło główne.
+ Zaawansowane rozpoznawanie odblokowania
+ Usuń zaawansowany klucz odblokowujący
+ Połącz swoje hasło ze zeskanowanymi danymi biometrycznymi lub danymi logowania urządzenia, aby szybko odblokować bazę danych.
+ Zaawansowane odblokowywanie bazy danych
+ Limit czasu zaawansowanego odblokowywania
+ Czas trwania zaawansowanego odblokowywania przed usunięciem jego zawartości
+ Wygaśnięcie zaawansowanego odblokowania
+ Nie przechowuj żadnych zaszyfrowanych treści, aby korzystać z zaawansowanego odblokowywania
+ Naciśnij, aby usunąć zaawansowane klucze odblokowujące
+ Zawartość
+ Otwórz bazę danych z zaawansowanym rozpoznawaniem odblokowania
+ Argon2id
+ Argon2d
+ Błąd zaawansowanego odblokowywania: %1$s
+ Nie można poprawnie odbudować listy.
+ Nie można pobrać identyfikatora URI bazy danych.
+ Dodano sugestie autouzupełniania.
+ Sugestie wbudowane
+ Spróbuj wyświetlić sugestie autouzupełniania bezpośrednio z kompatybilnej klawiatury
+ Zaawansowane odblokowywanie tymczasowe
+ Nie można zainicjować monitu odblokowania zaawansowanego.
+ Otwórz monit odblokowania zaawansowanego, aby odblokować bazę danych
+ Otwórz monit odblokowania zaawansowanego, aby odblokować bazę danych
+ Dostęp do pliku odwołany przez menedżera plików, zamknij bazę danych i otwórz ją ponownie z jej lokalizacji.
+ Nadpisz zewnętrzne modyfikacje, zapisując bazę danych lub przeładuj ją z najnowszymi zmianami.
+ Informacje zawarte w pliku bazy danych zostały zmodyfikowane poza aplikacją.
+ Załaduj ponownie bazę danych
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 88d79de82..229432453 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -173,7 +173,7 @@
Algoritmo de encriptação usado para todos os dados.
Para gerar a chave para o algoritmo de criptografia, a chave mestra é transformada usando uma função de derivação de chave com sal aleatoriamente.
Uso de memória
- Quantidade de memória (in bytes) a ser usada pela função de derivação de chave.
+ Quantidade de memória a ser usada pela função de derivação de chave.
Paralelismo
Grau de paralelismo (ou seja, número de threads) usado pela função de derivação de chave.
Ordenar
@@ -257,8 +257,6 @@
\nGrupos (~pastas) organizam suas entradas e sua base de dados.
Pesquise suas entradas
Entre com título, nome de usuário ou outros campos para recuperar facilmente suas senhas.
- Desbloqueio do banco de dados por Impressão digital
- Faça o link entre sua senha e sua impressão digital para rapidamente desbloquear seu banco de dados.
Modifique a entrada
Edite a sua entrada com campos personalizados. Os conjuntos de dados podem ser referenciados entre campos de entradas diferentes.
Criar uma senha forte
@@ -296,7 +294,6 @@
Contribuir
ChaCha20
AES
- Argon2
Tema do aplicativo
Tema usado no aplicativo
Pacote de ícones
@@ -398,7 +395,7 @@
Número máximo
Limitar o número de itens do histórico por entrada
Tamanho máximo
- Limitar o tamanho do histórico (em bytes) por entrada
+ Limitar o tamanho do histórico por entrada
Renovação recomendada
Recomende a mudança da chave mestra (em dias)
Forçar renovação
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 59caf2e96..6557fdcc2 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -157,7 +157,7 @@
- Média
- Grande
- ASCII Estendido
+ ASCII Extendido
Permitir
Não foi possível abrir a sua base de dados.
Não foi possível carregar a chave. Tente descarregar o \"Uso de Memória\" do KDF.
@@ -174,7 +174,7 @@
Criar base de dados
Para gerar uma chave para o algoritmo de encriptação, a chave mestre comprimida (SHA-256) é transformada usando uma função de derivação de chave (com um salt aleatório).
Uso de memória
- Quantidade de memória (em bytes) a ser usada pela função de derivação de chave.
+ Quantidade de memória a ser usada pela função de derivação de chave.
Paralelismo
Grau de paralelismo (ou seja, número de threads) usado pela função de derivação de chave.
Ordenar
@@ -247,8 +247,6 @@
\nGrupos (~pastas) organizam as suas entradas na sua base de dados.
Pesquise suas entradas
Entre com título, nome de utilizador ou outros campos para recuperar facilmente as suas palavras-passe.
- Desbloqueio da base de dados por biométricos
- Ligue a sua palavra-passe e o seu biométrico gravado para rapidamente desbloquear a sua base de dados.
Modifique a entrada
Edite a sua entrada com campos personalizados. Os conjuntos de dados podem ser referenciados entre campos de entradas diferentes.
Crie uma palavra-passe forte
@@ -283,7 +281,6 @@
Contribuir
ChaCha20
AES
- Argon2
Tema da app
Tema usado na app
Pacote de ícones
@@ -301,7 +298,7 @@
\n
\n\"Somente leitura\" evita que faça alterações não intencionais na base de dados.
\n\"Gravação\" permite-o adicionar, apagar ou modificar todos os elementos.
- Mostrar nomes de utilizador em listas de entrada
+ Mostrar nomes de utilizador na lista entradas
Área de transferência
Magikeyboard
Magikeyboard (KeePassDX)
@@ -402,7 +399,7 @@
Configurações de segurança
Desativar
Mostrar o botão de bloqueio na interface do utilizador
- Limitar o tamanho do histórico (em bytes) por entrada
+ Limitar o tamanho do histórico por entrada
A chave secreta deve estar em formato Base32.
Ativar
Compressão dos dados
@@ -456,7 +453,7 @@
OTP
Nenhum
Gravar a base de dados automaticamente depois de uma ação importante (somente no modo \"Modificável\")
- Mão pode copiar um grupo aqui.
+ Não pode copiar um grupo aqui.
Apagar histórico
Ao menos uma credencial deve ser definida.
Forçar renovação
@@ -494,4 +491,31 @@
\n
\nA sua base de dados pode se tornar muito grande e reduzir o desempenho com este envio.
Informações sobre credenciais
+ Credencial do dispositivo
+ Digite a palavra-chave, e depois clique no botão \"Desbloqueio avançado\".
+ Incapaz de inicializar o desbloqueio antecipado.
+ Erro de desbloqueio avançado: %1$s
+ Não conseguia reconhecer impressão de desbloqueio avançado
+ Não consegue ler a chave de desbloqueio avançada. Por favor, apague-a e repita o procedimento de desbloqueio de reconhecimento.
+ Extrair credencial de base de dados com dados de desbloqueio avançados
+ Base de dados aberta com reconhecimento avançado de desbloqueio
+ Advertência: Ainda precisa de se lembrar da sua palavra-passe principal se usar o reconhecimento avançado de desbloqueio.
+ Reconhecimento avançado de desbloqueio
+ Abrir o alerta de desbloqueio avançado para armazenar as credenciais
+ Abrir o alerta de desbloqueio avançado para desbloquear a base de dados
+ É necessária uma actualização de segurança biométrica.
+ O escaneamento biométrico é suportado, mas não configurado.
+ Acesso ao ficheiro revogado pelo gestor do ficheiro, fechar a base de dados e reabri-la a partir da sua localização.
+ Sobregravar as modificações externas, guardando a base de dados ou recarregando-a com as últimas alterações.
+ A informação contida no seu ficheiro de base de dados foi modificada fora da aplicação.
+ Apagar permanentemente todos os nós do caixote do lixo da reciclagem\?
+ Modo de registo
+ Modo Guardar
+ Modo de pesquisa
+ Apagar chave de desbloqueio avançada
+ Recarregar base de dados
+ Incapaz de reconstruir adequadamente a lista.
+ O URI da base de dados não pode ser recuperado.
+ O nome do campo já existe.
+ Salvar um novo item não é permitido numa base de dados só de leitura
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index b19ab708d..948728c13 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -20,7 +20,6 @@
Pacote de ícones
Tema usado na app
Tema da app
- Argon2
AES
ChaCha20
Twofish
@@ -131,7 +130,7 @@
A gravar a base de dados…
Grau de paralelismo (ou seja, número de threads) usado pela função de derivação de chave.
Paralelismo
- Quantidade de memória (em bytes) a ser usada pela função de derivação de chave.
+ Quantidade de memória a ser usada pela função de derivação de chave.
Uso de memória
Rodadas adicionais de criptografia adicionam mais proteção contra ataques de força bruta, mas podem tornar o processo de carregar e gravar mais lentos.
Rodadas de criptografia
@@ -194,7 +193,6 @@
Configurar OTP
Envie um anexo à sua entrada para gravar dados externos importantes.
Adicionar anexo
- Desbloqueio da base de dados por biométricos
Permite tocar no botão \"Abrir\" se nenhumas credenciais estiverem selecionadas
Remove anexos contidos na base de dados, mas não ligados a uma entrada
Remover dados não ligados
@@ -290,7 +288,7 @@
Esvaziar cesto da reciclagem
Gravar base de dados
Não foi possível gravar a base de dados.
- Ligue a sua palavra-passe e o seu biométrico gravado para rapidamente desbloquear a sua base de dados.
+ Ligue a sua palavra-passe e o seu biométrico gravado para rapidamente desbloquear a sua base de dados.
Configurações do teclado do aparelho
Ative um teclado customizado, populando suas palavras-passe e todos os campos de identidade
Gzip
@@ -304,7 +302,7 @@
Forçar renovação
Recomendar mudança da chave mestre (em dias)
Renovação recomendada
- Limitar o tamanho do histórico (em bytes) por entrada
+ Limitar o tamanho do histórico por entrada
Tamanho máximo
Limitar a quantidade de itens do histórico por entrada
Número máximo
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 2dafdff4d..a7d9acf4e 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -202,7 +202,7 @@
Rundele de transformare
Rundele suplimentare de criptare oferă o protecție mai mare împotriva atacurilor de forță brută, dar pot încetini cu adevărat încărcarea și economisirea.
Utilizarea memoriei
- Cantitatea de memorie (în octeți) care trebuie utilizată de funcția de derivare a cheilor.
+ Cantitatea de memorie care trebuie utilizată de funcția de derivare a cheilor.
Paralelism
Gradul de paralelism (adică numărul de fire) utilizat de funcția de derivare a cheilor.
Salvarea bazei de date …
@@ -282,7 +282,7 @@
Număr maxim
Limitați numărul de articole istorice pe intrare
Dimensiune maximă
- Limitați dimensiunea istoricului (în octeți) pe intrare
+ Limitați dimensiunea istoricului pe intrare
Recomandă reînnoirea
Recomandă schimbarea cheii master (zile)
Forteaza reinoirea
@@ -359,8 +359,6 @@
\nGrupuri (~ foldere) organizează intrări în baza de date.
Căutați prin intrări
Introduceți titlul, numele de utilizator sau conținutul altor câmpuri pentru a prelua parolele.
- Deblocarea bazei de date prin biometric
- Conectați parola la biometrica scanată pentru a debloca rapid baza de date.
Modificați intrarea
Modificați-vă înregistrarea cu câmpuri personalizate. Datele despre pool pot fi referențiate între diferite câmpuri de intrare.
Creați o parolă puternică pentru intrarea dvs.
@@ -408,7 +406,6 @@
Twofish
ChaCha20
AES
- Argon2
Tema aplicației
Tema folosită în aplicație
Pachet de pictograme
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 581c5166b..c9b312fc9 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -95,7 +95,7 @@
Строчные
Скрывать пароли
Скрывать пароли за (***) по умолчанию
- Сведения
+ О программе
Изменить главный пароль
Настройки
Настройки базы
@@ -168,7 +168,7 @@
Алгоритм шифрования базы для всех данных.
При создании ключа для алгоритма шифрования, главный пароль преобразуется при помощи функции формирования ключа со случайной солью.
Использование памяти
- Объём памяти (в байтах), который будет использоваться функцией формирования ключа.
+ Объём памяти, который будет использоваться функцией формирования ключа.
Уровень параллелизма
Уровень параллелизма (т.е. количество потоков), используемый функцией формирования ключа.
Сортировка
@@ -214,13 +214,13 @@
Блокировка
Блокировка экрана
Блокировать базу при отключении экрана
- Дополнительная разблокировка
- Сканирование биометрического ключа
+ Расширенная разблокировка
+ Биометрическая разблокировка
Включить разблокировку базы при помощи биометрического ключа
Удалить ключи шифрования
- Удалить все ключи шифрования, связанные с распознаванием биометрического ключа
- Невозможно запустить эту функцию.
- Ваша версия Android %1$s, но требуется %2$s.
+ Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки
+ Невозможно использовать эту функцию.
+ Ваша версия Android %1$s, требуется %2$s.
Соответствующее оборудование не найдено.
Имя файла
Путь
@@ -228,8 +228,8 @@
Создать новую базу
Использовать \"корзину\"
Перемещать группу или запись в \"корзину\" вместо удаления
- Особый шрифт
- Использовать в полях особый шрифт для лучшей читаемости
+ Особый шрифт полей
+ Использовать в полях специальный шрифт для лучшей читаемости
Доверять буферу обмена
Разрешить копирование пароля и защищённых полей в буфер обмена
Внимание: буфер обмена доступен всем приложениям. Если копируются чувствительные данные, другие программы могут их перехватить.
@@ -237,7 +237,7 @@
Описание базы
Версия базы
Текст
- Приложение
+ Внешний вид
Прочее
Клавиатура
Настройки Magikeyboard
@@ -260,21 +260,19 @@
\n
\nДобавляйте группы (аналог папок) для организации записей и баз.
Легко находите ваши записи
- Биометрическая разблокировка базы
- Установите взаимосвязь между паролем и биометрическим ключом для быстрой разблокировки базы.
Ищите записи по названию, имени или другим полям для быстрого доступа к своим паролям.
Редактируйте записи
- Редактируйте записи с настраиваемыми полями. Возможны перекрёстные ссылки между полями разных записей.
+ Редактируйте записи с пользовательскими полями. Возможны перекрёстные ссылки между полями разных записей.
Создайте надёжный пароль
Создайте надёжный пароль, связанный с записью, легко настраиваемый под критерии формы. И не забудьте главный пароль от базы.
- Добавляйте настраиваемые поля
+ Добавляйте пользовательские поля
Зарегистрируйте дополнительное поле, добавьте значение и при необходимости защитите его.
Разблокируйте базу
База только для чтения
Изменяйте режим открытия в сессии.
\n
\nВ \"режиме только для чтения\" можно предотвратить непреднамеренные изменения в базе.
-\nВ \"режиме записи\" вы можете добавлять, удалять или изменять любые элементы.
+\nВ \"режиме записи\" можно добавлять, удалять или изменять любые элементы.
Копируйте поля
Скопированные поля можно вставить в любом месте.
\n
@@ -297,10 +295,9 @@
Мы прилагаем все усилия, чтобы быстро выпустить эту функцию.
Не забывайте обновлять приложение.
Скачать
- Содействие
+ Помочь проекту
ChaCha20
AES
- Argon2
Тема приложения
Тема, используемая в приложении
Набор значков
@@ -326,7 +323,7 @@
Звук при нажатии
Не убивайте приложение…
Блокировка нажатием \"Назад\"
- Блокировка базы при нажатии кнопки \"Назад\" на начальном экране
+ Блокировка базы нажатием кнопки \"Назад\" на начальном экране
Очищать при закрытии
Блокировать базу при закрытии уведомления после использования или истечения времени ожидания
Корзина
@@ -357,10 +354,10 @@
Обновить
Закрыть поля
Невозможно создать базу с этим паролем и ключевым файлом.
- Дополнительная разблокировка
+ Расширенная разблокировка
Биометрический ключ
- Автозапрос биометрического ключа
- Автоматически запрашивать биометрический ключ, если он установлен для базы
+ Автозапрос ключа
+ Автоматически запрашивать расширенную разблокировку, если она установлена для базы
Включить
Отключить
Режим выбора
@@ -390,13 +387,13 @@
Исправить проблему путём создания новых UUID для дубликатов и продолжить работу\?
База открыта
Копирование полей ввода с помощью буфера обмена устройства
- Использовать дополнительную разблокировку для более лёгкого открытия базы данных
+ Использовать расширенную разблокировку для более лёгкого открытия базы данных
Сжатие данных
Сжатие данных уменьшает размер базы
Максимум записей
Ограничение числа элементов истории каждой записи
Максимальный размер
- Ограничение размера истории каждой записи (в байтах)
+ Ограничение размера истории каждой записи
Рекомендуемая смена
Рекомендовать менять главный пароль (в днях)
Принудительная смена
@@ -497,7 +494,6 @@
Сохранять данные поиска
Сохранять общую информацию при ручном выборе записи
Сохранять общие данные
- Невозможно получить доступ к зашифрованному объекту.
Блокировка базы
Автоматически переключаться на предыдущую клавиатуру после блокировки базы
Показывать UUID, связанный с записью
@@ -514,4 +510,46 @@
Режим записи
Режим поиска
Сохранение новых записей невозможно, т.к. база открыта только для чтения
+ Поле с таким именем уже существует.
+ Позволяет использовать учётные данные вашего устройства для открытия базы
+ Разблокировка учётными данными устройства
+ Учётные данные устройства
+ Введите пароль и нажмите кнопку \"Расширенная разблокировка\".
+ Невозможно инициализировать запрос расширенной разблокировки.
+ Невозможно распознать расширенную разблокировку
+ Невозможно прочитать ключ расширенной разблокировки. Удалите его и повторите процедуру распознавания разблокировки.
+ Извлекать учётные данные базы с использованием расширенной разблокировки
+ Открыть базу с расширенным распознаванием разблокировки
+ Предупреждение: даже при использовании расширенной разблокировки вам всё равно необходимо помнить главный пароль.
+ Открыть запрос расширенной разблокировки для сохранения учётных данных
+ Открыть запрос расширенной разблокировки для разблокировки базы
+ Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки\?
+ Ошибка расширенной разблокировки: %1$s
+ Распознавание расширенной разблокировки
+ Удалить ключ расширенной разблокировки
+ Ввод
+ Возврат к предыдущей клавиатуре
+ Пользовательские поля
+ Backspace
+ Выберите запись
+ Расширенная разблокировка базы
+ Срок действия расширенной разблокировки
+ Свяжите пароль с отсканированными биометрическими данными или учётными данными устройства, чтобы быстро разблокировать базу.
+ Продолжительность использования содержимого расширенной разблокировки до его удаления
+ Время действия
+ Временная расширенная разблокировка
+ Не сохранять зашифрованное содержимое для использования расширенной разблокировки
+ Нажмите, чтобы удалить ключи расширенной разблокировки
+ Содержимое
+ Argon2ID
+ Argon2D
+ Невозможно получить URI базы.
+ Невозможно правильно перестроить список.
+ Предложения автозаполнения добавлены.
+ Показывать предложения автозаполнения непосредственно в совместимой клавиатуре
+ Встроенные предложения
+ Доступ к файлу отозван файловым менеджером, закройте базу и снова откройте.
+ Сохранить базу, перезаписав внешние изменения, или перезагрузить её с последними изменениями.
+ Информация, содержащаяся в файле базы, была изменена вне этого приложения.
+ Перезагрузить базу
\ No newline at end of file
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 1ccb7debc..605a05c9b 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -16,8 +16,7 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see .
--->
-
+-->
Spätná väzba
Domovská stránka
Správca hesiel KeePass pre Android
@@ -26,7 +25,7 @@
Pridať Skupinu
Šifrovací algoritmus
Časový limit aplikácie
- Čas pred uzamknutím databázy, ak je aplikácia neaktívna.
+ Čas pred uzamknutím databázy, ak je aplikácia neaktívna
Aplikácia
Nastavenia aplikácie
Konzoly
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index ef278761d..4683cecef 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -184,7 +184,7 @@
Krypteringsalgoritm som används för all data i databasen.
För att generera nyckeln till krypteringsalgoritmen kommer huvudnyckeln transformeras med en slumpmässigt saltad nyckelderivatsfunktion.
Minnesanvändning
- Mängd minne (i bytes) som ska användas av nyckelderivatsfunktionen.
+ Mängd minne som ska användas av nyckelderivatsfunktionen.
Parallellism
Grad av parallellism (dvs. antal trådar) som används av nyckelderivatsfuntionen.
Sortera
@@ -281,7 +281,6 @@
Bidra
ChaCha20
AES
- Argon2
Apptema
Tema som används i appen
Ikonpaket
@@ -393,7 +392,7 @@
Max. historikobjekt
Begränsar antalet historikobjekt per post
Max. historikstorlek
- Begränsa historikens storlek per post (i bytes)
+ Begränsa historikens storlek per post
Rekommenderas att ändra
Rekommenderas att ändra huvudnyckeln (dagar)
Tvingad ändring
@@ -407,8 +406,6 @@
Gzip
Aktivera ett anpassat tangentbord som innehåller dina lösenord och alla identitetsfält
Enhetens tangentbordsinställningar
- Lås upp databasen med biometrik
- Länka dina lösenord till din skannande biometrik för att snabbt låsa upp din databas.
Kunde inte spara databas.
Spara databas
Töm papperskorgen
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index a8bf8c420..f72af2d2a 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -21,8 +21,8 @@
Ana sayfa
KeePass parola yöneticisinin Android uygulaması
Kabul et
- Girdi Ekle
- Girdi Düzenle
+ Girdi ekle
+ Girdi düzenle
Grup Ekle
Şifreleme
Şifreleme algoritması
@@ -32,7 +32,7 @@
Uygulama
Parantez
Genişletilmiş ASCII
- Veritabanı dosyaları oluşturmak, açmak ve kaydetmek için Niyet eylemini ACTION_CREATE_DOCUMENT ve ACTION_OPEN_DOCUMENT kabul eden bir dosya yöneticisi gerekir.
+ Niyet eylemini kabul eden bir dosya yöneticisi. ACTION_CREATE_DOCUMENT ve ACTION_OPEN_DOCUMENT veri tabanı dosyaları oluşturmak, açmak ve kaydetmek için gereklidir.
İzin ver
Pano temizlendi
Pano hatası
@@ -40,14 +40,14 @@
Pano temizlenemedi
Pano zaman aşımı
Panodaki depolama süresi (aygıtınız tarafından destekleniyorsa)
- Veritabanı
+ Veri tabanı
Erişildi
İptal
Notlar
Parolayı onayla
Oluşturuldu
Değiştirilmiş
- Giriş Verisi bulamadı.
+ Girdi verisi bulunamadı.
Parola
Kaydet
Başlık
@@ -58,9 +58,9 @@
Yolun doğru olduğundan emin olun.
Bir isim girin.
%1$s dosyasını panoya kopyalamak için seçin
- Veritabanı anahtarı alınıyor…
- Veritabanı içeriği deşifre ediliyor…
- Öntanımlı veritabanı olarak kullan
+ Veri tabanı anahtarı alınıyor…
+ Veri tabanı içeriğinin şifresi çözülüyor…
+ Öntanımlı veri tabanı olarak kullan
Rakamlar
KeePassDX © %1$d Kunzisoft <strong>açık kaynaklıdır</strong> ve <strong>reklam içermez</strong>.
\n<strong>GPLv3</strong> lisansı altında sağlanmaktadır, herhangi bir garanti vermez.
@@ -69,8 +69,8 @@
Arcfour akış şifresi desteklenmiyor.
Bu URI, KeePassDX\'te işlenemedi.
Bir anahtar dosyası seçin.
- Tüm veritabanınızı yüklemek için bellek yok.
- Veritabanınız yüklenemedi.
+ Tüm veri tabanınızı yükleyecek kadar bellek yok.
+ Veri tabanınız yüklenemedi.
Anahtar yüklenemedi. KDF \"Bellek Kullanımı\" nı azaltmaya çalışın.
En az bir parola oluşturma türü seçilmelidir.
Parolalar uyuşmuyor.
@@ -93,14 +93,14 @@
Parola
Kimlik bilgileri okunamadı.
Yanlış algoritma.
- Veritabanı biçimi tanımlanamadı.
+ Veri tabanı biçimi tanınamadı.
Anahtar dosya boş.
Uzunluk
Kullanıcı adlarını göster
- Giriş listelerinde kullanıcı adlarını göster
+ Girdi listelerinde kullanıcı adlarını göster
Liste ögelerinin boyutu
- Öğe listesindeki metin boyutu
- Veritabanı yükleniyor…
+ Öge listesindeki metin boyutu
+ Veri tabanı yükleniyor…
Küçük harf
Parolaları gizle
Parolaları öntanımlı olarak (***) ile maskele
@@ -110,7 +110,7 @@
Ayarlar
Uygulama ayarları
Form doldurma
- Veritabanı ayarları
+ Veri tabanı ayarları
Bağış Yap
Düzen
Kopyala
@@ -129,26 +129,26 @@
Asla
Arama sonucu bulunamadı
Bu URL\'u açmak için bir web tarayıcısı yükleyin.
- Mevcut veritabanını aç
- Yeni veritabanı oluştur
- Yedek girişleri arama
+ Mevcut veri tabanını aç
+ Yeni veri tabanı oluştur
+ Yedek girdilerde arama
Arama sonuçlarından \"Yedekleme\" ve \"Geri dönüşüm kutusu\" gruplarını atlar
- Yeni veritabanı oluştur…
+ Yeni veri tabanı oluşturuluyor…
Çalışıyor…
Koruma
Yazma korumalı
- Veritabanınızdaki herhangi bir şeyi değiştirmek için KeePassDX\'in yazma iznine ihtiyacı var.
+ Dosya yöneticinize bağlı olarak KeePassDX\'in depolama alanınıza yazmasına izin verilmeyebilir.
Kaldır
Kök
- Tüm veriler için veritabanı şifreleme algoritması kullanılmıştır.
+ Tüm veriler için kullanılan veri tabanı şifreleme algoritması.
Şifreleme algoritmasının anahtarını üretmek için ana anahtar, rastgele anahtar türetme işlevi kullanılarak dönüştürülür.
Dönüşüm turları
Ek şifreleme turları, kaba kuvvet saldırılarına karşı daha yüksek koruma sağlar, ancak yükleme ve kaydetmeyi gerçekten yavaşlatabilir.
Hafıza kullanımı
- Anahtar türetme işlevi tarafından kullanılacak bellek miktarı (bayt olarak).
+ Anahtar türetme işlevi tarafından kullanılacak bellek miktarı.
Paralellik
Anahtar türev fonksiyonu tarafından kullanılan paralellik derecesi (yani iplik sayısı).
- Veritabanı kaydediliyor…
+ Veri tabanı kaydediliyor…
Boşluk
Ara
Sırala
@@ -166,7 +166,7 @@
Arama Sonuçları
Eksi
Altı çizili
- Desteklenmeyen veritabanı sürümü.
+ Desteklenmeyen veri tabanı sürümü.
Büyük harf
Uyarı
Veri tabanı dosyasındaki metin kodlama biçiminin dışındaki parola karakterlerinden kaçın (tanınmayan karakterler aynı harfe dönüştürülür).
@@ -175,7 +175,6 @@
Sürüm %1$s
Yapı %1$s
Şifreli parola saklandı
- Bu veritabanının henüz bir parolası yok.
Geçmiş
Görünüm
Genel
@@ -190,16 +189,16 @@
İzin verilen parola üreticisi karakterlerini ayarla
Pano
Pano bildirimleri
- Bir girişi görüntülerken alanları kopyalamak için pano bildirimlerini göster
+ Bir girdiyi görüntülerken alanları kopyalamak için pano bildirimlerini göster
Panodaki otomatik silme başarısız olursa, geçmişini elle silin.
Kilit
Ekran kilidi
- Ekran kapalıyken veritabanını kilitle
- Parmakizi
- Parmak izi tarama
- Veritabanını açmak için biyometriklerinizi taramanızı sağlar
+ Ekran kapalıyken veri tabanını kilitle
+ Gelişmiş kilit açma
+ Biyometrik kilit açma
+ Veri tabanını açmak için biyometriklerinizi taramanızı sağlar
Şifreleme anahtarlarını silin
- Parmak izi tanıma ile ilgili tüm şifreleme anahtarlarını silin
+ Gelişmiş kilit açma tanıma ile ilgili tüm şifreleme anahtarlarını silin
Bu özellik başlatılamadı.
Aygıtta Android %1$s çalışıyor, ancak %2$s veya sonraki bir sürüm gerekli.
İlgili donanım bulunamadı.
@@ -208,16 +207,16 @@
Ana anahtar atayın
Geri dönüşüm kutusu kullanımı
Silmeden önce grupları ve girdileri \"Geri Dönüşüm Kutusu\"na taşır
- Yazı tipi alanı
+ Alan yazı tipi
Daha iyi karakter görünürlüğü için alanlarda kullanılan yazı tipini değiştirin
Pano güveni
- Giriş parolası ve korunan alanları panoya kopyalamaya izin ver
+ Girdi parolası ve korunan alanları panoya kopyalamaya izin ver
Uyarı: Pano tüm uygulamalar tarafından paylaşılır. Hassas veriler kopyalanırsa, diğer yazılımlar bu verileri kurtarabilir.
- Veritabanı adı
- Veritabanı açıklaması
- Veritabanı sürümü
+ Veri tabanı adı
+ Veri tabanı açıklaması
+ Veri tabanı sürümü
Metin
- Uygulama
+ Arayüz
Diğer
Klavye
Magikeyboard
@@ -227,14 +226,14 @@
Magikeyboard ayarları
Girdi
Zaman aşımı
- Klavye girişini temizlemek için zaman aşımı
+ Klavye girdisini temizlemek için zaman aşımı
Bildirim bilgisi
- Bir giriş mevcut olduğunda bir bildirim göster
+ Bir girdi kullanılabilir olduğunda bildirim göster
Girdi
- Magikeyboard\'da %1$s mevcut
+ %1$s Magikeyboard\'da kullanılabilir
%1$s
Kapanışta temizle
- Bildirimi kapatırken veritabanını kapatın
+ Bildirimi kapatırken veri tabanını kapat
Görünüm
Klavye teması
Anahtarlar
@@ -249,42 +248,40 @@
Eğitim ipuçlarını sıfırla
Tüm eğitim bilgilerini tekrar göster
Eğitim ipuçlarını sıfırla
- Veritabanı dosyanızı oluşturun
+ Veri tabanı dosyanızı oluşturun
İlk parola yönetim dosyanızı oluşturun.
- Mevcut bir veritabanını aç
- Kullanmaya devam etmek için önceki veritabanı dosyanızı dosya tarayıcınızdan açın.
+ Mevcut bir veri tabanını açın
+ Kullanmaya devam etmek için önceki veri tabanı dosyanızı dosya tarayıcınızdan açın.
Veri tabanınıza öge ekleyin
- Girdiler dijital kimliğinizi yönetmenize yardımcı olur.
-\n
-\nGruplar (~klasörler) veritabanınızdaki girdileri düzenler.
- Girişlerde ara
- Parolanızı kurtarmak için başlık, kullanıcı adı veya diğer alanların içeriğini girin.
- Parmak iziyle veri tabanı kilidi açma
- Veritabanınızı hızlıca açmak için parolanızı taranan parmak izinize bağlayın.
- Girdiyi düzenle
- Girdinizi özel alanlarla düzenleyin. Havuz verileri farklı giriş alanları arasında referans alınabilir.
- Güçlü bir parola oluşturun
- Girişinizle ilişkilendirmek için güçlü bir parola oluşturun, formun kriterlerine göre kolayca tanımlayın ve güvenli parolayı unutmayın.
- Özel alanlar ekle
- Ek bir alan kaydedin, bir değer ekleyin ve isteğe bağlı olarak koruyun.
- Veritabanınızın kilidini açın
- Veritabanınızın kilidini açmak için parola ve/veya anahtar dosya girin.
+ Girdiler dijital kimliğinizi yönetmenize yardımcı olur.
\n
-\nHer değişiklikten sonra veritabanı dosyanızı güvenli bir yerde yedekleyin.
- Veritabanınızı yazmaya karşı koru
+\nGruplar (~klasörler) veri tabanınızdaki girdileri düzenler.
+ Girdilerde arayın
+ Parolanızı kurtarmak için başlık, kullanıcı adı veya diğer alanların içeriğini girin.
+ Girdiyi düzenleyin
+ Girdinizi özel alanlarla düzenleyin. Havuz verileri farklı girdi alanları arasında referans alınabilir.
+ Güçlü bir parola oluşturun
+ Girdinizle ilişkilendirmek için güçlü bir parola oluşturun, formun kriterlerine göre kolayca tanımlayın ve güvenli parolayı unutmayın.
+ Özel alanlar ekleyin
+ Ek bir alan kaydedin, bir değer ekleyin ve isteğe bağlı olarak koruyun.
+ Veri tabanınızın kilidini açın
+ Veri tabanınızın kilidini açmak için parola ve/veya anahtar dosyası girin.
+\n
+\nHer değişiklikten sonra veri tabanı dosyanızı güvenli bir yerde yedekleyin.
+ Veri tabanınızı yazmaya karşı koruyun
Oturum için açılış modunu değiştir.
\n
-\n\"Yazma korumalı\", veritabanında istenmeyen değişiklikleri önler.
-\n\"Değiştirilebilir\", tüm öğeleri eklemenizi, silmenizi veya değiştirmenizi sağlar.
- Bir alan kopyala
+\n\"Yazma korumalı\", veri tabanında istenmeyen değişiklikleri önler.
+\n\"Değiştirilebilir\", tüm ögeleri istediğiniz gibi eklemenize, silmenize veya değiştirmenize izin verir.
+ Bir alanı kopyalayın
Kopyalanan alanlar herhangi bir yere yapıştırılabilir.
\n
\nTercih ettiğiniz form doldurma yöntemini kullanın.
- Veritabanını kilitle
- Veritabanınızı hızlıca kilitleyin, uygulamayı bir süre sonra kilitlemek için ve ekran kapandığında ayarlayabilirsiniz.
- Öge sıralama
+ Veri tabanını kilitleyin
+ Veri tabanınızı hızlıca kilitleyin, belirli bir süre sonra ve ekran kapandığında kilitlenmesi için uygulamayı ayarlayabilirsiniz.
+ Ögeleri sıralayın
Girdilerin ve grupların nasıl sıralandığını seçin.
- Katıl
+ Katılın
Daha fazla özellik ekleyerek istikrarı, güvenliği artırmaya yardımcı olun.
Birçok parola yönetimi uygulamasının aksine, bu uygulama <strong>reklam içermez</strong>, <strong> copyleft lisanslı özgür yazılımdır</strong> ve hangi sürümü kullanırsanız kullanın, sunucularında kişisel veri toplamaz.
Profesyonel sürümü satın alarak, bu <strong>görsel stile</strong> erişebilecek ve özellikle <strong>topluluk projelerinin gerçekleştirilmesine</strong> yardımcı olacaksınız.
@@ -294,7 +291,7 @@
<strong>Pro</strong> sürümü satın alarak,
<strong>Katkıda</strong> bulunarak,
geliştiricilerin <strong>yeni özellikler</strong> oluşturmasını ve söz konusu hatalara göre <strong>hataları düzeltmesini</strong> teşvik ediyorsunuz.
- Katkınız için çok teşekkür ederim.
+ Katkıda bulunduğunuz için çok teşekkür ederim.
Bu özelliği çabucak yayınlamak için çok çalışıyoruz.
Yeni sürümleri yükleyerek uygulamanızı güncel tutmayı unutmayın.
İndir
@@ -303,37 +300,36 @@
Twofish
ChaCha20
AES
- Argon2
Uygulama teması
Uygulamada kullanılan tema
Simge paketi
Uygulamada kullanılan simge paketi
Seçim modu
Uygulamayı öldürmeyin…
- Kullanıcı kök ekranda geri düğmesine tıkladığında veritabanını kilitle
+ Kullanıcı kök ekranda geri düğmesine tıkladığında veri tabanını kilitle
Kapanışta temizle
Panonun süresi dolduğunda veya kullanmaya başladıktan sonra bildirim kapatıldığında veri tabanını kilitle
Dosya aç
Düğüm ekle
- Girdi Ekle
+ Girdi ekle
Grup ekle
Dosya bilgileri
Parola onay kutusu
Anahtar dosyası onay kutusu
- Giriş simgesi
+ Girdi simgesi
Parola üreteci
Parola uzunluğu
Alan ekle
Alanı kaldır
UUID
- Buraya bir girişi taşıyamazsınız.
- Buraya bir girişi kopyalayamazsınız.
- Giriş sayısını göster
- Bir gruptaki girişlerin sayısını göster
+ Bir girdiyi buraya taşıyamazsınız.
+ Bir girdiyi buraya kopyalayamazsınız.
+ Girdi sayısını göster
+ Bir gruptaki girdilerin sayısını göster
Kilitlemek için \'Geri\'ye basın
Geri dönüşüm kutusu
- Giriş seçimi
- Bir girişi görüntülerken Magikeyboard\'da giriş alanlarını göster
+ Girdi seçimi
+ Bir girdiyi görüntülerken Magikeyboard\'da giriş alanlarını göster
Parolayı sil
Veri tabanına bağlantı denemesinden sonra girilen parolayı siler
Alt düğüm
@@ -341,11 +337,11 @@
Arka plan
Güncelleme
Alanları kapat
- Bu parola ve anahtar dosyası ile veritabanı oluşturulamıyor.
+ Bu parola ve anahtar dosyası ile veri tabanı oluşturulamıyor.
Gelişmiş kilit açma
Biyometrik
- Biyometrik taramayı otomatik aç
- Vari tabanı, onu kullanacak biçimde ayarlandıysa biyometriği otomatik olarak sor
+ Otomatik açma istemi
+ Vari tabanı onu kullanacak biçimde ayarlandıysa otomatik olarak gelişmiş kilit açma isteğinde bulun
Etkinleştir
Devre dışı
Ana anahtar
@@ -367,20 +363,20 @@
Periyot %1$d ile %2$d saniye arasında olmalıdır.
Belirteç %1$d ile %2$d basamak içermelidir.
%1$s aynı UUID değerine sahip %2$s zaten var.
- Veritabanı oluşturuluyor…
+ Veri tabanı oluşturuluyor…
Güvenlik ayarları
Ana anahtar ayarları
- Veritabanı tekrarlanan UUID\'ler içermektedir.
+ Veri tabanı tekrarlanan UUID\'ler içeriyor.
Tekrarlananlar için yeni UUID\'ler oluşturarak sorunu çöz ve devam et\?
- Veritabanı açıldı
- Aygıtınızın panosunu kullanarak giriş alanlarını kopyala
- Veritabanını daha kolay açmak için gelişmiş kilit açma özelliğini kullan
+ Veri tabanı açıldı
+ Aygıtınızın panosunu kullanarak girdi alanlarını kopyalayın
+ Veri tabanını daha kolay açmak için gelişmiş kilit açma özelliğini kullan
Veri sıkıştırma
Veri sıkıştırma veri tabanı boyutunu azaltır
Azami sayı
Girdi başına geçmiş ögelerinin sayısını sınırla
Azami boyut
- Giriş başına geçmiş boyutunu (bayt olarak) sınırlama
+ Girdi başına geçmiş boyutunu sınırla
Yenilemeyi öner
Ana anahtarın değiştirilmesini öner (gün)
Yenilemeyi zorla
@@ -388,19 +384,19 @@
Bir dahaki sefere yenilemeye zorla
Bir dahaki sefere ana anahtarı değiştirmeyi gerektirir (bir kez)
Öntanımlı kullanıcı adı
- Özel veritabanı rengi
+ Özel veri tabanı rengi
Sıkıştırma
Yok
Gzip
Aygıt klavye ayarları
- Veritabanı kaydedilemedi.
+ Veri tabanı kaydedilemedi.
Veri tabanını kaydet
Geri dönüşüm kutusunu boşalt
Komut çalıştırılıyor…
Seçilen düğümler kalıcı olarak silinsin mi\?
Anahtar deposu düzgün bir şekilde başlatılmadı.
Geri dönüşüm kutusu grubu
- Veritabanını otomatik kaydet
+ Veri tabanını otomatik kaydet
Her önemli işlemden sonra veri tabanını kaydet (\"Değiştirilebilir\" modda)
Ekler
Geçmişi geri yükle
@@ -415,9 +411,9 @@
Süresi dolmuş girdileri gizle
Süresi dolmuş girdiler gösterilmez
Veri tabanı değişikliklerini kaydetmek için dosya yazma erişimi ver
- Son veritabanları listesindeki bozuk bağlantıları gizle
- Bozuk veritabanı bağlantılarını gizle
- Son veritabanlarının konumlarını göster
+ Son veri tabanları listesindeki bozuk bağlantıları gizle
+ Bozuk veri tabanı bağlantılarını gizle
+ Son veri tabanlarının konumlarını göster
Son dosyaları göster
Anahtar dosyalarının nerede saklandığını takip eder
Anahtar dosyası konumlarını hatırla
@@ -428,8 +424,8 @@
Katkı
İletişim
İki ögeli kimlik doğrulaması (2FA) için istenen bir belirteç oluşturmak için bir kerelik parola yönetimini (HOTP / TOTP) ayarlayın.
- OTP ayarla
- Veritabanı dosyası oluşturulamıyor.
+ Bir kerelik parola (OTP) ayarlayın
+ Veri tabanı dosyası oluşturulamıyor.
<strong>Özgürlüğümüzü korumak</strong>, <strong>hataları düzeltmek</strong>, <strong>özellikler eklemek</strong> ve <strong>her zaman etkin olmak</strong> için, <strong>desteğinize</strong> güveniyoruz.
Ek ekle
Vazgeç
@@ -455,14 +451,14 @@
Alt etki alanı arama
Bu metin istenen ögeyle eşleşmiyor.
Öge ekle
- Otomatik tuş eylemini gerçekleştirdikten sonra otomatik olarak önceki klavyeye dön
+ \"Otomatik tuş eylemini\" gerçekleştirdikten sonra otomatik olarak önceki klavyeye dön
Otomatik tuş eylemi
Veri tabanı kimlik bilgileri ekranında otomatik olarak önceki klavyeye dön
Veri tabanı kimlik bilgileri ekranı
Klavye değiştir
%1$s yükle
Önemli harici verileri kaydetmek için girdinize bir ek yükleyin.
- Ek ekle
+ Ek ekleyin
Dosya yine de eklensin mi\?
Bu dosyanın yüklenmesi mevcut dosyanın yerini alacaktır.
Bir KeePass veri tabanının sadece küçük yardımcı dosyaları (PGP anahtar dosyaları gibi) içermesi beklenmektedir.
@@ -490,7 +486,6 @@
Elle girdi seçimi yaparken paylaşılan bilgileri kaydetmeyi dene
Paylaşılan bilgileri kaydet
Bildirim
- Şifreleme nesnesi alınamıyor.
Biyometrik güvenlik güncellemesi gerekli.
Biyometrik bilgiler veya aygıt kimlik doğrulama bilgileri kaydedilmedi.
Geri dönüşüm kutusundaki tüm düğümler kalıcı olarak silinsin mi\?
@@ -498,4 +493,47 @@
Kaydetme modu
Arama modu
Salt okunur bir veri tabanında yeni bir öge kaydetmeye izin verilmiyor
+ Alan adı zaten var.
+ Gelişmiş kilit açma tanıma ile ilgili tüm şifreleme anahtarları silinsin mi\?
+ Veri tabanını açmak için aygıt kimlik bilgilerinizi kullanmanıza olanak tanır
+ Aygıt kimlik bilgisiyle kilit açma
+ Aygıt kimlik bilgileri
+ Parolayı yazın ve ardından \"Gelişmiş kilit açma\" düğmesine tıklayın.
+ Gelişmiş kilit açma istemi başlatılamıyor.
+ Bu veri tabanında henüz saklanmış kimlik bilgisi yok.
+ Gelişmiş kilit açma hatası: %1$s
+ Gelişmiş kilit açma parmak izi tanınamadı
+ Gelişmiş kilit açma anahtarı okunamıyor. Lütfen silin ve kilit açma tanıma işlemini tekrarlayın.
+ Veri tabanı kimlik bilgilerini gelişmiş kilit açma özelliğiyle çıkarın
+ Veri tabanını gelişmiş kilit açma tanıma ile aç
+ Uyarı: Gelişmiş kilit açma tanıma kullanırsanız, yine de ana parolanızı hatırlamanız gerekmektedir.
+ Gelişmiş kilit açma tanıma
+ Kimlik bilgilerini saklamak için gelişmiş kilit açma istemini aç
+ Veri tabanının kilidini açmak için gelişmiş kilit açma istemini aç
+ Gelişmiş kilit açma anahtarını sil
+ Enter
+ Backspace
+ Girdi seç
+ Önceki klavyeye geri dön
+ Özel alanlar
+ Veri tabanınızın kilidini hızlı bir şekilde açmak için parolanızı taranmış biyometrik veya aygıt kimlik bilgilerinize bağlayın.
+ Gelişmiş veri tabanı kilidi açma
+ Gelişmiş kilit açma zaman aşımı
+ İçeriğini silmeden önce gelişmiş kilit açma kullanımının süresi
+ Gelişmiş kilit açma süre sonu
+ Gelişmiş kilit açmayı kullanmak için herhangi bir şifrelenmiş içeriği saklama
+ Geçici gelişmiş kilit açma
+ Gelişmiş kilit açma anahtarlarını silmek için dokunun
+ İçerik
+ Argon2id
+ Argon2d
+ Liste düzgün şekilde yeniden oluşturulamıyor.
+ Veri tabanı URI\'si alınamıyor.
+ Otomatik doldurma önerileri eklendi.
+ Doğrudan uyumlu bir klavyeden otomatik doldurma önerileri görüntülemeye çalış
+ Satır içi öneriler
+ Dosyaya erişim dosya yöneticisi tarafından iptal edildi, veri tabanını kapatın ve bulunduğu yerden yeniden açın.
+ Veri tabanını kaydederek veya en son değişikliklerle yeniden yükleyerek harici değişikliklerin üzerine yazın.
+ Veri tabanı dosyanızda bulunan bilgiler, uygulamanın dışında değiştirildi.
+ Veri tabanını yeniden yükle
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index c81b85438..e8c80deca 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -25,11 +25,11 @@
Додати групу
Алгоритм шифрування
Час очікування застосунку
- Час очікування до блокування бази даних
+ Час бездіяльности до блокування бази даних
Застосунок
Параметри застосунку
Дужки
- Для створення, відкриття та збереження файлів баз даних потрібен файловий менеджер, який приймає дії Intent ACTION_CREATE_DOCUMENT та ACTION_OPEN_DOCUMENT.
+ Для створення, відкриття та збереження файлів баз даних потрібен файловий менеджер, який приймує дії Intent ACTION_CREATE_DOCUMENT та ACTION_OPEN_DOCUMENT.
Буфер обміну очищено
Час до очищення буфера обміну
Тривалість зберігання в буфері обміну (якщо підтримується пристроєм)
@@ -66,7 +66,7 @@
Необхідно вибрати принаймні один тип створення пароля.
Паролі не збігаються.
Забагато циклів. Встановлено 2147483648.
- Введіть додатне ціле число до поля \"Довжина\".
+ Введіть додатне ціле додатне число до поля \"Довжина\".
Файловий менеджер
Згенерувати пароль
Підтвердити пароль
@@ -154,7 +154,7 @@
Gzip
Стиснення
Інше
- Застосунок
+ Інтерфейс
Текст
Версія бази даних
Власний колір бази даних
@@ -206,7 +206,7 @@
Поле файла ключа
Налаштування головного ключа
Параметри безпеки
- Розширені параметри розблокування
+ Розширене розблокування
Заповнення форм
Скопійовано %1$s
Створення бази даних…
@@ -227,7 +227,7 @@
Не вдалося зберегти базу даних.
Неможливо створити базу даних із цим паролем та файлом ключа.
Не вдалося створити файл бази даних.
- Ви не можете скопіювати групу сюди.
+ Ви не можете копіювати групу сюди.
Ви не можете копіювати записи сюди.
Ви не можете перемістити запис сюди.
Ви не можете перемістити групу в себе саму.
@@ -271,7 +271,6 @@
Набір піктограм
Тема застосунку
Тема застосунку
- Argon2
AES
ChaCha20
Готово!
@@ -281,7 +280,7 @@
Завантаження %1$s
Допомогти
Завантажити
- Не забудьте постійно оновлювати застосунок, встановлюючи нові версії.
+ Не забувайте оновлювати застосунок, встановлюючи нові версії.
Ми докладаємо всіх зусиль для якнайшвидшого випуску цієї функції.
Щиро вдячні за вашу допомогу.
ви заохочуєте розробників створювати <strong>нові функції</strong> та <strong>виправляти помилки</strong> відповідно до ваших зауважень.
@@ -305,7 +304,7 @@
Змініть режим сеансу.
\n
\n\"Захист від запису\" запобігає внесенню випадкових змін до бази даних.
-\n\"Дозволити зміни\" дозволяє вам додавати, видаляти чи змінювати будь-які елементи.
+\n\"Дозволити зміни\" дозволяє додавати, видаляти чи змінювати будь-які елементи.
Зберігати базу даних після кожної важливої дії (у режимі \"Дозволити зміни\")
Захистіть вашу базу даних від запису
Розблокуйте вашу базу даних
@@ -317,8 +316,6 @@
Створіть надійний пароль
Додавайте власні поля до записів. Дані полів різних записів можна повʼязувати між собою.
Змінюйте записи
- Зв\'яжіть пароль та скановані біометричні дані для швидкого розблокування бази даних.
- Біометричне розблокування бази даних
Введіть назву, ім\'я користувача або вміст інших полів для пошуку паролів.
Шукайте поміж записів
Створюйте записи для керування вашими обліковими записами.
@@ -355,7 +352,7 @@
%1$s доступне на Magikeyboard
Запис
Час очікування до очищення клавіатури від запису
- Час очікування
+ Обмеження часу
Закрити базу даних під час закриття сповіщення
Очищати під час закриття
Показувати сповіщення, коли запис доступний
@@ -380,7 +377,7 @@
Примусова зміна
Рекомендувати змінити головний ключ (днів)
Рекомендувати змінити
- Обмежити об\'єм історії (в байтах) кожного запису
+ Обмежити об\'єм історії кожного запису
Максимальний об\'єм
Обмежити кількість елементів історії кожного запису
Максимальна кількість
@@ -394,17 +391,17 @@
Назва файлу
Пристрій працює під керуванням Android %1$s, але необхідний %2$s чи пізніші.
Не вдалось запустити цю функцію.
- Видалити всі ключі шифрування, пов\'язані з розпізнаванням біометричного ключа
+ Видалити всі ключі шифрування, пов\'язані з розпізнаванням розширеного розблокування
Видалити ключі шифрування
- Автоматично запитувати біометричний ключ, якщо базу даних налаштовано для роботи з ним
- Автоматично запитувати біометричний ключ
+ Автоматично запитувати розширене розблокування, якщо базу даних налаштовано для роботи з ним
+ Автозапит ключа
Дозволяє сканувати біометричні дані, щоб відкрити базу даних
Розблокування біометричним ключем
Увімкніть розширені параметри розблокування та спростіть відкриття бази даних
Розширені параметри розблокування
Показувати кнопку блокування в інтерфейсі користувача
Показувати кнопку блокування
- Блокувати базу даних, коли користувач натискає кнопку назад на головному екрані
+ Блокувати базу даних, коли користувач натисне кнопку назад на головному екрані
Натисніть \'Назад\', щоб заблокувати
Блокувати базу даних, якщо екран вимкнено
Блокування екрана
@@ -450,7 +447,7 @@
Спочатку групи
За зростанням ↓
Впорядкувати
- Об\'єм пам\'яті (у байтах), необхідний для функції створення ключа.
+ Об\'єм пам\'яті, необхідний для функції створення ключа.
Для створення ключа алгоритму шифрування, головний ключ перетворюється за допомогою функції створення випадково приготованого ключа.
Не закривайте застосунок…
Виконання команди…
@@ -472,8 +469,8 @@
Фільтр
Додати елемент
Перемикання клавіатури
- Автоматично перемикатися до попередньої клавіатури після виконання дії Самочинного введення
- Самочинне введення
+ Автоматично перемикатися до попередньої клавіатури після виконання дії «Автоматична дія кнопки»
+ Автоматична дія кнопки
Автоматичне перемикання до попередньої клавіатури, на екрані входу до бази даних
Екран входу до бази даних
База даних KeePass має містити лише невеликі файли утиліт (наприклад, файли ключів PGP).
@@ -506,7 +503,6 @@
Намагатися зберегти спільні відомості під час вибору запису власноруч
Збереження спільних відомостей
Сповіщення
- Не вдалося отримати крипто-об\'єкт.
Необхідно оновити біометричний захист.
Біометричних чи облікових даних пристрою не зареєстровано.
Видалити всі вузли з кошика остаточно\?
@@ -514,4 +510,46 @@
Режим збереження
Режим пошуку
Збереження нового елемента заборонено в базі даних лише для читання
+ Назва поля вже існує.
+ Видалити всі ключі шифрування, пов\'язані з розширеним розпізнаванням розблокування\?
+ Дає змогу використовувати облікові дані пристрою для відкриття бази даних
+ Розблокування облікових даних пристрою
+ Облікові дані пристрою
+ Введіть пароль, а потім натисніть кнопку «Розширене розблокування».
+ Не вдалося ініціалізувати запит на розширене розблокування.
+ Помилка розширеного розблокування: %1$s
+ Не вдалося розпізнати розширене розблокування
+ Не вдалося розпізнати ключ розширеного розблокування. Видаліть його та повторіть процедуру створення ключа.
+ Витягування облікових даних бази даних за допомогою даних розширеного розблокування
+ Відкрити базу даних розширеним розпізнаванням розблокування
+ Попередження: Навіть якщо ви користуєтеся розширеним розблокуванням, вам однаково необхідно пам\'ятати головний пароль.
+ Розпізнавання розширеного розблокування
+ Відкривати запит розширеного розблокування, щоб зберегти облікові дані
+ Відкривати запит розширеного розблокування, щоб розблокувати базу даних
+ Видалити ключ розширеного розблокування
+ Enter
+ Backspace
+ Вибрати запис
+ Повернутися до попередньої клавіатури
+ Власні поля
+ Час дії розширеного розблокування
+ Час дії розширеного розблокування
+ Пов’яжіть свій пароль зі сканованими біометричними даними або даними пристрою, щоб швидко розблокувати базу даних.
+ Розширене розблокування бази даних
+ Тривалість використання розширеного розблокування перед видаленням його вмісту
+ Не зберігайте зашифрований вміст для використання розширеного розблокування
+ Тимчасове розширене розблокування
+ Торкнутися, щоб видалити клавіші розширеного розблокування
+ Вміст
+ Argon2id
+ Argon2d
+ Не вдалося належним чином відновити список.
+ Неможливо отримати URI бази даних.
+ Додано пропозиції автозаповнення.
+ Спробувати показ пропозицій автозаповнення безпосередньо з сумісної клавіатури
+ Вбудовані пропозиції
+ Доступ до файлу скасовано менеджером файлів, закрийте базу даних і знову відкрийте її з її розташування.
+ Перезаписати зовнішні зміни, зберігши базу даних або перезавантажте її з найновішими змінами.
+ Відомості, що містяться у файлі бази даних, змінено за межами застосунку.
+ Перезавантажити базу даних
\ No newline at end of file
diff --git a/app/src/main/res/values-v23/donottranslate.xml b/app/src/main/res/values-v23/donottranslate.xml
index e761117a7..e1e50e325 100644
--- a/app/src/main/res/values-v23/donottranslate.xml
+++ b/app/src/main/res/values-v23/donottranslate.xml
@@ -19,4 +19,5 @@
-->
true
+ true
diff --git a/app/src/main/res/values-v30/donottranslate.xml b/app/src/main/res/values-v30/donottranslate.xml
new file mode 100644
index 000000000..fc50571b3
--- /dev/null
+++ b/app/src/main/res/values-v30/donottranslate.xml
@@ -0,0 +1,22 @@
+
+
+
+ true
+
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 4bc8c9d24..f6d58bb79 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -29,7 +29,7 @@
应用
程序设置
括号
- 新建与保存数据库需要支持打开和新建文件的文件管理器。
+ 需要一款接受意图操作 ACTION_CREATE_DOCUMENT 和 ACTION_OPEN_DOCUMENT 的文件管理器来创建、打开和保存数据库文件。
剪贴板已清空
剪贴板清空延时
剪贴板保存时间 (若您的设备支持该功能)
@@ -145,7 +145,6 @@
无法清空剪切板
ChaCha20
AES
- Argon2
主题
图标包
程序中使用的图标包
@@ -179,7 +178,7 @@
加密所有数据时采用的算法。
将迭代主密钥以生成加密数据库所需的密钥,转换方式为随机加盐算法。
内存使用量
- 密钥推导算法使用的内存(以二进制字节计)。
+ 密钥推导算法使用的内存。
并行
密钥推导算法使用的并行运行程度(即线程数量)。
排序
@@ -208,7 +207,7 @@
数据库描述
数据库版本
文字样式
- 程序
+ 界面
其他
键盘
魔法键盘
@@ -243,14 +242,14 @@
生物识别解锁
通过生物识别解锁数据库
删除加密密钥
- 删除所有与生物识别相关的密钥
+ 删除所有与高级解锁识别相关的加密密钥
无法启动此功能。
此设备运行 Android %1$s ,但应用需要 %2$s 或更高版本。
找不到所需的硬件。
分配主密钥
回收站使用情况
删除群组和条目前先移至回收站
- 字段字体
+ 字段字型
更改字段字体,可以使字符更清楚
允许复制整段密码和受保护的字段至剪切板
警告:复制密码时密码在剪贴板中,而所有程序都可访问剪切板。因此复制密码时,设备上的其他程序也能看到密码。
@@ -291,8 +290,6 @@
\n还可以使用群组来管理数据库中的条目。
搜索条目
输入标题、用户名或其他字段的内容来搜索密码。
- 正在解锁生物识别数据库
- 将主密钥与生物识别信息关联,以快速解锁数据库。
编辑此条目
使用自定义字段编辑条。自定义字段可以在不同的条目间引用。
新建一个强密码
@@ -301,10 +298,10 @@
添加一个新的字段并添加为其添加一个值,此时可以选择是否保护该字段及其值。
解锁数据库
数据库启用写入保护(只读)
- 更改会话的打开模式。
-\n
-\n“写入保护(只读)”可防止对数据库的意外更改。
-\n“可编辑(可写)”允许您添加、删除或者修改元素。
+ 更改此会话的打开模式。
+\n
+\n“写入保护(只读)”可防止对数据库的意外更改。
+\n“可编辑”允许您随心所欲地添加、删除或者修改所有元素。
复制字段
已复制的字段可以粘贴到任何地方。
\n
@@ -325,11 +322,11 @@
您的留言,是对开发人员添加<strong>新功能</strong>及<strong>修复 bugs</strong> 的鼓励。
非常感谢您的捐助和贡献。
我们正在努力的研发并尽快发布新特性。
- 别忘了更新程序。
+ 别忘了通过安装新版本来保持你的应用程序是最新的。
选择模式
不要终止程序…
按返回键以锁定
- 点击屏幕底部的返回键时锁定数据库
+ 当用户单击根屏幕上的返回按钮时锁定数据库
关闭程序时清空剪贴板
剪贴板持续时间过期或通知在您开始使用后关闭时,锁定数据库
回收站
@@ -362,8 +359,8 @@
无法使用此密码和密钥文件新建数据库。
高级解锁
生物识别
- 自动打开生物识别提示
- 生物识别密钥已配置时自动打开提示
+ 自动打开提示
+ 自动请求高级解锁,如果数据库设置为使用它
启用
禁用
主密钥
@@ -379,7 +376,7 @@
一次性密码
错误的一次性密码密钥。
至少需要设置一个凭据。
- 不能将群组复制到这里。
+ 这里不能复制组。
密钥必须是BASE32格式。
计数器必须在%1$d和%2$d之间。
时长必须在%1$d秒到%2$d秒之间。
@@ -398,7 +395,7 @@
最大数量
限制每个条目的历史记录条数
最大大小
- 限制每个条目的历史记录大小(以字节为单位)
+ 限制每个条目的历史记录大小
建议修改
建议修改主密钥(以天为单位)
强制修改
@@ -472,7 +469,7 @@
文本和请求的条目不匹配.
添加条目
数据库凭据屏幕
- 执行自动键动作后,自动切换回前一个键盘
+ 执行“自动键动作”后,自动切换回前一个键盘
自动键动作
如果显示数据库凭据屏幕,则自动返回到上一个键盘
切换键盘
@@ -506,7 +503,6 @@
手动选择条目时,尝试保存共享信息
保存分享的信息
通知
- 无法检索加密对象。
需要生物识别安全更新。
未登记生物识别或设备凭证。
从回收站永久删除所有节点?
@@ -514,4 +510,46 @@
保存模式
搜索模式
只读数据库不允许保存新条目
+ 字段名已经存在。
+ 删除所有与高级解锁识别相关的加密密钥吗?
+ 允许您使用设备凭据来打开数据库
+ 设备凭据解锁
+ 设备凭据
+ 输入密码,然后点击“高级解锁”按钮。
+ 无法初始化高级解锁提示。
+ 高级解锁出错:%1$s
+ 无法识别高级解锁印记
+ 无法读取高级解锁密钥。请删除它,并重复解锁识别步骤。
+ 用高级解锁数据提取数据库凭据
+ 用高级解锁识别打开数据库
+ 警告:即使您使用高级解锁识别,您仍然需要记住您的主密码。
+ 高级解锁识别
+ 打开高级解锁提示来存储凭证
+ 打开高级解锁提示来解锁数据库
+ 删除高级解锁密钥
+ 输入
+ 退格键
+ 选择条目
+ 回到先前的键盘
+ 自定义字段
+ 将您的密码连接到您扫描的生物特征或设备凭据,以快速解锁您的数据库。
+ 高级数据库解锁
+ 高级解锁超时
+ 删除内容之前高级解锁使用的持续时间
+ 高级解锁过期
+ 不要存储任何加密内容来使用高级解锁
+ 临时性高级解锁
+ 点击删除高级解锁密钥
+ 内容
+ Argon2id
+ Argon2d
+ 无法正确地重建列表。
+ 无法检索数据库 URI 。
+ 已添加自动填充建议。
+ 尝试直接从兼容的键盘显示自动填充建议
+ 内联建议
+ 文件管理器撤销了对此文件的访问,关闭数据库并从其位置重新打开它。
+ 通过保存数据库或用最新的更改重新加载数据库来覆盖外部修改。
+ 数据库文件中包含的信息已在应用程序之外被修改。
+ 重新加载数据库
\ No newline at end of file
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index bfa602c58..299a25928 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -94,11 +94,15 @@
advanced_unlock_explanation_key
biometric_unlock_enable_key
false
- biometric_auto_open_prompt_key
- false
- biometric_delete_all_key_key
device_credential_unlock_enable_key
false
+ biometric_auto_open_prompt_key
+ false
+ temp_advanced_unlock_enable_key
+ false
+ temp_advanced_unlock_timeout_key
+ 36000000
+ biometric_delete_all_key_key
settings_form_filling_key
@@ -147,6 +151,8 @@
false
autofill_auto_search_key
true
+ autofill_inline_suggestions_key
+ false
autofill_save_search_info_key
true
autofill_ask_to_save_data_key
@@ -175,7 +181,7 @@
true
hide_expired_entries_key
false
- hide_expired_entries_key
+ show_uuid_key
false
enable_education_screens_key
true
@@ -287,6 +293,20 @@
- 1800000
- -1
+
+ - 300000
+ - 900000
+ - 1800000
+ - 3600000
+ - 7200000
+ - 18000000
+ - 36000000
+ - 86400000
+ - 172800000
+ - 604800000
+ - 2592000000
+ - -1
+
32dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 94d50d479..3aaa72ef0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,8 +16,7 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see .
--->
-
+-->
Contact
Contribution
Feedback
@@ -37,7 +36,7 @@
App
Brackets
Extended ASCII
- A file manager that accepts the Intent action ACTION_CREATE_DOCUMENT and ACTION_OPEN_DOCUMENT is needed to create, open and save database files.
+ A file manager that accepts the ACTION_CREATE_DOCUMENT and ACTION_OPEN_DOCUMENT intent action is required to create, open, and save database files.
Allow
Clipboard cleared
Clipboard error
@@ -119,12 +118,12 @@
\"Transformation rounds\" too high. Setting to 2147483648.
Each string must have a field name.
This label already exists.
- Enter a positive whole number in the \"Length\" field.
+ Enter a positive integer number in the \"Length\" field.
Could not enable autofill service.
You can not move a group into itself.
You can not move an entry here.
You can not copy an entry here.
- You can not copy a group here.
+ You cannot copy a group here.
Unable to create database file.
Unable to create database with this password and keyfile.
Could not save database.
@@ -132,9 +131,12 @@
Counter must be between %1$d and %2$d.
Period must be between %1$d and %2$d seconds.
Token must contain %1$d to %2$d digits.
+ The existing OTP type is not recognized by this form, its validation may no longer correctly generate the token.
This text does not match the requested item.
Saving a new item is not allowed in a read-only database
The field name already exists.
+ Database URI cannot be retrieved.
+ Unable to properly rebuild the list.
Field name
Field value
Could not find file. Try reopening it from your file browser.
@@ -184,6 +186,7 @@
Hide password
Lock database
Save database
+ Reload database
Open
Search
Show password
@@ -231,7 +234,7 @@
Transformation rounds
Additional encryption rounds provide higher protection against brute force attacks, but can really slow down loading and saving.
Memory usage
- Amount of memory (in bytes) to be used by the key derivation function.
+ Amount of memory to be used by the key derivation function.
Parallelism
Degree of parallelism (i.e. number of threads) used by the key derivation function.
Saving database…
@@ -271,6 +274,9 @@
Remove this data anyway?
It is not recommended to add an empty keyfile.
The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.
+ The information contained in your database file has been modified outside the app.
+ Overwrite the external modifications by saving the database or reload it with the latest changes.
+ Access to the file revoked by the file manager, close the database and reopen it from its location.
Version %1$s
Build %1$s
No biometric or device credential is enrolled.
@@ -288,7 +294,6 @@
Advanced unlock error: %1$s
This database does not have stored credential yet.
Unable to initialize advanced unlock prompt.
- Unable to retrieve crypto object.
Type in the password, and then click the \"Advanced unlock\" button.
History
Appearance
@@ -318,7 +323,9 @@
Lock the database when the user clicks the back button on the root screen
Show lock button
Displays the lock button in the user interface
+ Content
Advanced unlock
+ Tap to delete advanced unlocking keys
Use advanced unlocking to open a database more easily
Biometric unlocking
Lets you scan your biometric to open the database
@@ -326,6 +333,11 @@
Lets you use your device credential to open the database
Auto-open prompt
Automatically request advanced unlock if the database is set up to use it
+ Temp advanced unlocking
+ Do not store any encrypted content to use advanced unlocking
+ Advanced unlocking expiration
+ Duration of advanced unlocking usage before deleting its content
+ Advanced unlocking timeout
Delete encryption keys
Delete all encryption keys related to advanced unlock recognition
Delete all encryption keys related to advanced unlock recognition?
@@ -346,14 +358,14 @@
Maximum number
Limit the number of history items per entry
Maximum size
- Limit the history size (in bytes) per entry
+ Limit the history size per entry
Recommend renewal
Recommend changing the master key (days)
Force renewal
Require changing the master key (days)
Force renewal next time
Require changing the master key the next time (once)
- Field font
+ Field typeface
Change font used in fields for better character visibility
Clipboard trust
Allow copying the entry password and protected fields to the clipboard
@@ -369,7 +381,7 @@
Custom database color
Database version
Text
- App
+ Interface
Other
Compression
None
@@ -412,10 +424,17 @@
Automatically switch back to the previous keyboard after executing \"Auto key action\"
Lock database
Automatically switch back to the previous keyboard after locking the database
+ Custom fields
+ Back to previous keyboard
+ Select entry
+ Backspace
+ Enter
Close database
Close the database after an autofill selection
Auto search
Automatically suggest search results from the web domain or application ID
+ Inline suggestions
+ Attempt to display autofill suggestions directly from a compatible keyboard
Save search info
Try to save search information when making a manual entry selection
Ask to save data
@@ -427,6 +446,7 @@
Block autofill
Restart the app containing the form to activate the blocking.
Data save is not allowed for a database opened as read-only.
+ Autofill suggestions added.
Allow no master key
Allows tapping the \"Open\" button if no credentials are selected
Delete password
@@ -448,8 +468,8 @@
Entries help manage your digital identities.\n\nGroups (~folders) organize entries in your database.
Search through entries
Enter title, username or content of other fields to retrieve your passwords.
- Biometric database unlocking
- Link your password to your scanned biometric to quickly unlock your database.
+ Advanced database unlocking
+ Link your password to your scanned biometric or device credential to quickly unlock your database.
Edit the entry
Edit your entry with custom fields. Pool data can be referenced between different entry fields.
Create a strong password
@@ -463,7 +483,10 @@
Unlock your database
Enter the password and/or keyfile to unlock your database.\n\nBackup your database file in a safe place after each change.
Write protect your database
- Change opening mode for the session.\n\n\"Write-protected\" prevents unintended changes to the database.\n\"Modifiable\" lets you add, delete, or modify all elements.
+ Change opening mode for the session.
+\n
+\n\"Write-protected\" prevents unintended changes to the database.
+\n\"Modifiable\" lets you add, delete, or modify all the elements as you wish.
Copy a field
Copied fields can be pasted anywhere.\n\nUse the form filling method you prefer.
Lock the database
@@ -482,7 +505,7 @@
you are encouraging developers to create <strong>new features</strong> and to <strong>fix bugs</strong> according to your remarks.
Thanks a lot for your contribution.
We are working hard to release this feature quickly.
- Do not forget to keep your app up to date by installing new versions.
+ Remember to keep your app up to date by installing new versions.
Download
Contribute
Download %1$s
@@ -495,7 +518,12 @@
Twofish
ChaCha20
AES
- Argon2
+ Argon2d
+ Argon2id
+ B
+ KiB
+ MiB
+ GiB
- 5 seconds
- 10 seconds
@@ -507,6 +535,20 @@
- 30 minutes
- Never
+
+ - 5 minutes
+ - 15 minutes
+ - 30 minutes
+ - 1 hour
+ - 2 hours
+ - 5 hours
+ - 10 hours
+ - 24 hours
+ - 48 hours
+ - 1 week
+ - 1 month
+ - Never
+
- Small
- Medium
@@ -529,4 +571,4 @@
Expired entries are not shown
Show UUID
Displays the UUID linked to an entry
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 76406e20a..ca871ad06 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -465,6 +465,7 @@
- @android:color/transparent
- @null
- true
+ - true
- true
- false
diff --git a/app/src/main/res/xml/dataset_service.xml b/app/src/main/res/xml/dataset_service.xml
index ea2597ec7..6d595b7b5 100644
--- a/app/src/main/res/xml/dataset_service.xml
+++ b/app/src/main/res/xml/dataset_service.xml
@@ -23,7 +23,9 @@ Settings Activity. This is pointed to in the service's meta-data in the applicat
+ android:settingsActivity="com.kunzisoft.keepass.settings.AutofillSettingsActivity"
+ android:supportsInlineSuggestions="true"
+ tools:ignore="UnusedAttribute">
diff --git a/app/src/main/res/xml/keyboard_password.xml b/app/src/main/res/xml/keyboard_password.xml
index d6c8cfbb7..50e128b21 100644
--- a/app/src/main/res/xml/keyboard_password.xml
+++ b/app/src/main/res/xml/keyboard_password.xml
@@ -18,6 +18,8 @@
-->
@@ -26,26 +28,30 @@
+ android:isRepeatable="false" />
diff --git a/app/src/main/res/xml/keyboard_password_entry.xml b/app/src/main/res/xml/keyboard_password_entry.xml
index 566974b13..72dcbe389 100644
--- a/app/src/main/res/xml/keyboard_password_entry.xml
+++ b/app/src/main/res/xml/keyboard_password_entry.xml
@@ -18,6 +18,8 @@
-->
@@ -26,25 +28,36 @@
android:codes="520"
android:keyEdgeFlags="left"
android:keyIcon="@drawable/ic_link_black_24dp"
- android:keyWidth="24%p"
+ android:tooltipText="@string/entry_url"
+ android:keyWidth="15%p"
android:horizontalGap="1%p"
android:isRepeatable="false"/>
+
@@ -54,6 +67,7 @@
diff --git a/app/src/main/res/xml/preferences_advanced_unlock.xml b/app/src/main/res/xml/preferences_advanced_unlock.xml
index 9ce2402fd..0b66ceefb 100644
--- a/app/src/main/res/xml/preferences_advanced_unlock.xml
+++ b/app/src/main/res/xml/preferences_advanced_unlock.xml
@@ -39,6 +39,23 @@
android:title="@string/biometric_auto_open_prompt_title"
android:summary="@string/biometric_auto_open_prompt_summary"
android:defaultValue="@bool/biometric_auto_open_prompt_default"/>
+
+
+
+
+
diff --git a/app/src/main/res/xml/preferences_database.xml b/app/src/main/res/xml/preferences_database.xml
index 151bbb57d..c4fabd5dc 100644
--- a/app/src/main/res/xml/preferences_database.xml
+++ b/app/src/main/res/xml/preferences_database.xml
@@ -101,7 +101,7 @@
android:title="@string/max_history_items_title"
android:positiveButtonText="@string/save"
android:negativeButtonText="@string/entry_cancel"/>
-
-
+
+
+
diff --git a/art/ic_fingerprint_notification.svg b/art/ic_fingerprint_notification.svg
new file mode 100644
index 000000000..aaa185bc0
--- /dev/null
+++ b/art/ic_fingerprint_notification.svg
@@ -0,0 +1,109 @@
+
+
+
+
diff --git a/build.gradle b/build.gradle
index cde378402..8e5371d69 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.4.10'
+ ext.kotlin_version = '1.4.21'
repositories {
jcenter()
google()
diff --git a/fastlane/metadata/android/en-US/changelogs/47.txt b/fastlane/metadata/android/en-US/changelogs/47.txt
index 0310fcc24..87a5613f5 100644
--- a/fastlane/metadata/android/en-US/changelogs/47.txt
+++ b/fastlane/metadata/android/en-US/changelogs/47.txt
@@ -1 +1,6 @@
- * Unlock database by device credentials (PIN/Password/Pattern) #779 #102
\ No newline at end of file
+ * Unlock database by device credentials (PIN/Password/Pattern) #779 #102
+ * Advanced unlock with timeout #102 #437 #566
+ * Remove default database parameter when the file is no longer accessible #803
+ * Move OTP button to the first view level in Magikeyboard #587
+ * Tooltips for Magikeyboard #586
+ * Fix small bugs #805
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/48.txt b/fastlane/metadata/android/en-US/changelogs/48.txt
new file mode 100644
index 000000000..d5ae30391
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/48.txt
@@ -0,0 +1,2 @@
+ * Fix small bugs #812
+ * Argon2ID implementation #791
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/49.txt b/fastlane/metadata/android/en-US/changelogs/49.txt
new file mode 100644
index 000000000..af4f00858
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/49.txt
@@ -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
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/50.txt b/fastlane/metadata/android/en-US/changelogs/50.txt
new file mode 100644
index 000000000..457eb4856
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/50.txt
@@ -0,0 +1 @@
+ * Fix KeyFile bug #820
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/51.txt b/fastlane/metadata/android/en-US/changelogs/51.txt
new file mode 100644
index 000000000..25c598fe9
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/51.txt
@@ -0,0 +1,2 @@
+ * Fix small bugs
+ * Remove write permission since Android 10
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/52.txt b/fastlane/metadata/android/en-US/changelogs/52.txt
new file mode 100644
index 000000000..5edd71c36
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/52.txt
@@ -0,0 +1,2 @@
+ * Fix specific attachments with kdbx3.1 databases #828
+ * Fix small bugs
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/53.txt b/fastlane/metadata/android/en-US/changelogs/53.txt
new file mode 100644
index 000000000..f9ef200fe
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/53.txt
@@ -0,0 +1,8 @@
+ * Detect file changes and reload database #794
+ * Inline suggestions autofill with compatible keyboard (Android R) #827
+ * Add Keyfile XML version 2 #844
+ * Fix binaries of 64 bytes #835
+ * Special search in title fields #830
+ * Priority to OTP button in notifications #845
+ * Fix OTP generation for long secret key #848
+ * Fix small bugs #849
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/54.txt b/fastlane/metadata/android/en-US/changelogs/54.txt
new file mode 100644
index 000000000..b96a30ff4
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/54.txt
@@ -0,0 +1,2 @@
+ * Try to fix autofill #852
+ * Fix database change dialog displayed too often #853
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/55.txt b/fastlane/metadata/android/en-US/changelogs/55.txt
new file mode 100644
index 000000000..65ebabbb8
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/55.txt
@@ -0,0 +1,2 @@
+ * Add Keyfile XML version 2 (fix hex) #844
+ * Fix hex Keyfile #861
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/56.txt b/fastlane/metadata/android/en-US/changelogs/56.txt
new file mode 100644
index 000000000..60621bf81
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/56.txt
@@ -0,0 +1,6 @@
+ * Fix OTP token type #863
+ * Fix auto open biometric prompt #862
+ * Fix back appearance setting #865
+ * Fix orientation change in settings #872
+ * Change memory unit to MiB #851
+ * Small changes #642
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/57.txt b/fastlane/metadata/android/en-US/changelogs/57.txt
new file mode 100644
index 000000000..e9c7c3f47
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/57.txt
@@ -0,0 +1,3 @@
+ * Fix TOTP plugin settings #878
+ * Allow Emoji #796
+ * Scroll and better UI in entry edition screen #876
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/47.txt b/fastlane/metadata/android/fr-FR/changelogs/47.txt
index 5c7e79bd4..4491102fa 100644
--- a/fastlane/metadata/android/fr-FR/changelogs/47.txt
+++ b/fastlane/metadata/android/fr-FR/changelogs/47.txt
@@ -1 +1,6 @@
- * Déverouillage de base de données avec identifiants de l'appareil (PIN/Password/Pattern) #779 #102
\ No newline at end of file
+ * Déverouillage de base de données avec identifiants de l'appareil (PIN/Password/Pattern) #779 #102
+ * Déverouillage avancé avec expiration #102 #437 #566
+ * Supprimer le parmètre de base de données par défaut quand le fichier n'est plus accessible #803
+ * Déplacer le bouton OTP vers le premier niveau de vue dans le Magiclavier #587
+ * Info-bulles pour le Magiclavier #586
+ * Correction petits bugs #805
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/48.txt b/fastlane/metadata/android/fr-FR/changelogs/48.txt
new file mode 100644
index 000000000..a04fb074b
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/48.txt
@@ -0,0 +1,2 @@
+ * Correction de petits bugs #812
+ * Implementation d'Argon2ID #791
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/49.txt b/fastlane/metadata/android/fr-FR/changelogs/49.txt
new file mode 100644
index 000000000..da28437c5
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/49.txt
@@ -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
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/50.txt b/fastlane/metadata/android/fr-FR/changelogs/50.txt
new file mode 100644
index 000000000..45cc3d57d
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/50.txt
@@ -0,0 +1 @@
+ * Correction du bug de fichier de clé #820
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/51.txt b/fastlane/metadata/android/fr-FR/changelogs/51.txt
new file mode 100644
index 000000000..18233cde2
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/51.txt
@@ -0,0 +1,2 @@
+ * Correction de petits bugs
+ * Suppression des permissions d'écriture à partir d'Android 10
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/52.txt b/fastlane/metadata/android/fr-FR/changelogs/52.txt
new file mode 100644
index 000000000..c3ebc1548
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/52.txt
@@ -0,0 +1,2 @@
+ * Correction des pièces jointes spécifiques avec les bases kdbx3.1 #828
+ * Correction de petits bugs
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/53.txt b/fastlane/metadata/android/fr-FR/changelogs/53.txt
new file mode 100644
index 000000000..b6ec26240
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/53.txt
@@ -0,0 +1,8 @@
+ * Détection des changements de fichiers et rechargement de base de données #794
+ * Remplissage automatique avec suggestions en ligne (Android R) (Android R) #827
+ * Ajout du fichier de clé XML version 2 #844
+ * Correction des binaires de 64 Octets #835
+ * Recherche spéciale dans les champs de recherche #830
+ * Priorité au bouton OTP dans les notifications #845
+ * Correction de génération OTP pour longue clé secrère #848
+ * Correction de petits bugs #849
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/54.txt b/fastlane/metadata/android/fr-FR/changelogs/54.txt
new file mode 100644
index 000000000..e11d58628
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/54.txt
@@ -0,0 +1,2 @@
+ * Tentative de résolution de l'autofill #852
+ * Correction du dialogue de changement de base de données affiché trop souvent #853
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/55.txt b/fastlane/metadata/android/fr-FR/changelogs/55.txt
new file mode 100644
index 000000000..fff6228b0
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/55.txt
@@ -0,0 +1,2 @@
+ * Ajout de fichier de clé XML version 2 (correction hex) #844
+ * Correction de fichier de clé hex #861
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/56.txt b/fastlane/metadata/android/fr-FR/changelogs/56.txt
new file mode 100644
index 000000000..61fec4409
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/56.txt
@@ -0,0 +1,6 @@
+ * Correction du type de token OTP #863
+ * Correction de l'ouverture automatique de l'invite biométrique #862
+ * Correction du retour dans les paramètres d'apparence #865
+ * Correction du changement d'orientation dans les paramètres #872
+ * Modification des unités de mémoire en MiB #851
+ * Petits changements #642
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/changelogs/57.txt b/fastlane/metadata/android/fr-FR/changelogs/57.txt
new file mode 100644
index 000000000..8885da30c
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/57.txt
@@ -0,0 +1,3 @@
+ * Correction des paramètres des plugins TOTP #878
+ * Autoriser les Emoji #796
+ * Défilement et amélioration de l'UI dans l'écran d'édition #876
\ No newline at end of file
diff --git a/fastlane/pro/metadata/android/en-US/full_description.txt b/fastlane/pro/metadata/android/en-US/full_description.txt
index 144c9e3c7..9ecd13423 100644
--- a/fastlane/pro/metadata/android/en-US/full_description.txt
+++ b/fastlane/pro/metadata/android/en-US/full_description.txt
@@ -1,7 +1,7 @@
Multi-format KeePass password manager, the app allows saving and using passwords, keys and digital identities in a secure way, by integrating the Android design standards.
This pro version is under development, buying it encourages faster development, better service, and you contribute to the creation of open source softwares without advertising.
-Currently, the application has the same features as the free version with the themes unlocked but is intended to integrate the elements of connection and synchronization facilitated for sites and services commonly used.
+Currently, the application has the same features as the free version with the themes unlocked but is intended to integrate elements related to non-free sites and services commonly used.
Features
- Create database files / entries and groups.
diff --git a/fastlane/pro/metadata/android/fr-FR/full_description.txt b/fastlane/pro/metadata/android/fr-FR/full_description.txt
index 2969463ba..5342ba8db 100644
--- a/fastlane/pro/metadata/android/fr-FR/full_description.txt
+++ b/fastlane/pro/metadata/android/fr-FR/full_description.txt
@@ -1,7 +1,7 @@
Gestionnaire de mots de passe KeePass multiformats, l'application permet d'enregistrer et d'utiliser des mots de passe, des clés et des identités numériques de manière sécurisée, en intégrant les normes de conception Android.
Cette version pro est en cours de développement, en l'achetant vous encouragez un développement plus rapide, un meilleur service et vous contribuez à la création de logiciels open source sans publicité.
-Actuellement, l'application possède les mêmes fonctionnalités que la version gratuite avec les thèmes débloqués mais est destinée à intégrer les éléments de connexion et de synchronisation facilités pour les sites et services couramment utilisés.
+Actuellement, l'application possède les mêmes fonctionnalités que la version gratuite avec les thèmes débloqués mais est destinée à intégrer des éléments liés à des sites et services non gratuits couramment utilisés.
Fonctionnalités
- Création de bases de données / entrées et groupes.
diff --git a/fastlane/pro/metadata/android/ja-JP/full_description.txt b/fastlane/pro/metadata/android/ja-JP/full_description.txt
index 2ff47dfee..d6f7237e4 100644
--- a/fastlane/pro/metadata/android/ja-JP/full_description.txt
+++ b/fastlane/pro/metadata/android/ja-JP/full_description.txt
@@ -1,7 +1,7 @@
複数の形式に対応する KeePass パスワード マネージャー。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。
この pro バージョンは開発中です。購入することで開発の加速とサービスの改善を支援し、広告なしのオープンソース ソフトウェアの作成に貢献できます。
-現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。よく利用されるサイトやサービスの接続と同期を楽にする要素を統合することが予定されています。
+現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。一般的に使われている不自由なサイトやサービスに関連する要素を統合することを計画しています。
機能
- データベースファイル / エントリー・グループの作成