mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8c7acf7ce | ||
|
|
17a356ae76 | ||
|
|
bd847e632d | ||
|
|
2bfb9b048d | ||
|
|
dc40b50b65 | ||
|
|
3e2271e596 | ||
|
|
4b4fd2a11d | ||
|
|
23468290df | ||
|
|
a276f6aa06 | ||
|
|
f2a58361a1 | ||
|
|
271023b528 | ||
|
|
e1771ca249 | ||
|
|
ca4f4bd151 | ||
|
|
d81454d618 | ||
|
|
fb43c1c624 | ||
|
|
9e060f878d | ||
|
|
bd9c21ee8a | ||
|
|
3e6d40e8da | ||
|
|
79683cb3fc | ||
|
|
52a2090a31 | ||
|
|
3dfe4ace7b | ||
|
|
bd0d17b134 | ||
|
|
6b0ccc1780 | ||
|
|
d75ac4b825 | ||
|
|
b60d610d02 | ||
|
|
f7a5c5d0ea | ||
|
|
28f79aec11 | ||
|
|
778d963fbf | ||
|
|
a765bc84e7 | ||
|
|
804ecc1baa | ||
|
|
d331c3dc03 | ||
|
|
7010d2f86a | ||
|
|
b1d6117eb2 | ||
|
|
f3b814388d | ||
|
|
b62996a57c | ||
|
|
a49e056f02 | ||
|
|
a6dece16bf | ||
|
|
8e3ddd64d2 | ||
|
|
45a847fa3e | ||
|
|
6b6f03b143 | ||
|
|
5446efca4a | ||
|
|
8d04a7f90b | ||
|
|
626495c19e | ||
|
|
e5a198f524 | ||
|
|
161524843f | ||
|
|
5550e7dea3 | ||
|
|
64f66c290c | ||
|
|
e8925b3c0b | ||
|
|
cf67ce04a8 | ||
|
|
84ee4ca2c7 | ||
|
|
27eb095720 | ||
|
|
d273f21819 | ||
|
|
455fd0cd6d | ||
|
|
c5a8650c81 | ||
|
|
b5f9bbed5e | ||
|
|
e789741090 | ||
|
|
5c6d93bc57 | ||
|
|
697b672038 | ||
|
|
2d9e9c24a8 | ||
|
|
5fb281c800 | ||
|
|
96896c1c42 | ||
|
|
d7052bd9e6 | ||
|
|
8b23932788 | ||
|
|
50912c6966 | ||
|
|
53b51934b9 | ||
|
|
a8a3685965 | ||
|
|
149b67e28b | ||
|
|
a83032bffa | ||
|
|
a5d3a153bf | ||
|
|
4210c155eb | ||
|
|
4bf110a9b1 | ||
|
|
50f2684500 | ||
|
|
e95424b8f9 | ||
|
|
8462882707 | ||
|
|
a5c8d25f64 | ||
|
|
689ce2f9b3 | ||
|
|
54246533ac | ||
|
|
66e4b0fe47 | ||
|
|
3e8ae3e2e3 | ||
|
|
d856ef3772 | ||
|
|
5727880ac7 | ||
|
|
ec4302a780 | ||
|
|
d4203598a1 | ||
|
|
a278c8c718 | ||
|
|
faf5f4b51a | ||
|
|
b2f503b326 | ||
|
|
beb5484bf6 | ||
|
|
ec63d75349 | ||
|
|
4c0e79b245 | ||
|
|
50a77684c1 | ||
|
|
8bb84b486d | ||
|
|
4b05f2536f | ||
|
|
d6f968fe7e | ||
|
|
ed758edd44 | ||
|
|
94b7fce2e5 | ||
|
|
dbd9c6cbb7 | ||
|
|
0f6376fb80 | ||
|
|
9522328238 | ||
|
|
e6ad716119 | ||
|
|
440006bb08 | ||
|
|
ea289ef7cf | ||
|
|
352b171484 | ||
|
|
969ab56bf8 | ||
|
|
062a9852e5 | ||
|
|
dd77d7a5e6 | ||
|
|
f60e32522a | ||
|
|
070a91f19c | ||
|
|
0790e80670 | ||
|
|
20841e3d7b | ||
|
|
0caae233c3 | ||
|
|
5497d8fafb | ||
|
|
f69b43249c | ||
|
|
b606fd98f6 | ||
|
|
ba3b7b0f1f | ||
|
|
058d82dc36 | ||
|
|
6f0b0ac4fa | ||
|
|
35f87b0f94 | ||
|
|
0ead9ce9b4 | ||
|
|
80479a6a7c | ||
|
|
a7cea8201e | ||
|
|
081a7fa798 | ||
|
|
85782c4f93 | ||
|
|
d7b7df26d7 | ||
|
|
b6b1c8e31d | ||
|
|
17156f7ca2 | ||
|
|
0761d356b8 | ||
|
|
6da747ce6f | ||
|
|
87b1a1f527 | ||
|
|
72a8a55faf | ||
|
|
9a6a709746 | ||
|
|
428b53cc56 | ||
|
|
e688859e32 | ||
|
|
98336da116 | ||
|
|
c037e443b0 | ||
|
|
d339a50e0a | ||
|
|
7d836f2633 | ||
|
|
45d8470b4c | ||
|
|
1ca3bfe472 | ||
|
|
066da83d70 | ||
|
|
44ab881751 | ||
|
|
5ab3cf985a | ||
|
|
f271f2b181 | ||
|
|
91d75be0ea | ||
|
|
e18b3436c9 | ||
|
|
fcb1b5ae6b |
21
CHANGELOG
21
CHANGELOG
@@ -1,3 +1,24 @@
|
||||
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
|
||||
|
||||
@@ -5,15 +5,15 @@ 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 = 49
|
||||
versionName = "2.9.5"
|
||||
versionCode = 53
|
||||
versionName = "2.9.9"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
@@ -92,7 +92,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
def room_version = "2.2.5"
|
||||
def room_version = "2.2.6"
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
@@ -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')
|
||||
|
||||
@@ -14,10 +14,15 @@
|
||||
android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission
|
||||
android:name="android.permission.VIBRATE"/>
|
||||
<!-- Write permission until Android 10 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<!-- Open apps from links -->
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ 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
|
||||
@@ -53,7 +54,9 @@ 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_RELOAD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
@@ -151,6 +154,10 @@ class EntryActivity : LockingActivity() {
|
||||
if (result.isSuccess)
|
||||
finish()
|
||||
}
|
||||
ACTION_DATABASE_RELOAD_TASK -> {
|
||||
// Close the current activity
|
||||
finish()
|
||||
}
|
||||
}
|
||||
coordinatorLayout?.showActionError(result)
|
||||
}
|
||||
@@ -199,8 +206,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
|
||||
@@ -232,23 +238,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 =
|
||||
@@ -278,11 +282,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
|
||||
@@ -292,44 +294,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,24 +341,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
|
||||
@@ -369,8 +365,6 @@ class EntryActivity : LockingActivity() {
|
||||
|
||||
// Assign special data
|
||||
entryContentsView?.assignUUID(entry.nodeId.id)
|
||||
|
||||
database.stopManageEntry(entry)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
@@ -408,6 +402,9 @@ class EntryActivity : LockingActivity() {
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
menu.findItem(R.id.menu_edit)?.isVisible = false
|
||||
}
|
||||
if (mSpecialMode != SpecialMode.DEFAULT) {
|
||||
menu.findItem(R.id.menu_reload_database)?.isVisible = false
|
||||
}
|
||||
|
||||
val gotoUrl = menu.findItem(R.id.menu_goto_url)
|
||||
gotoUrl?.apply {
|
||||
@@ -501,6 +498,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)
|
||||
|
||||
@@ -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
|
||||
@@ -49,6 +48,7 @@ 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
|
||||
@@ -61,6 +61,7 @@ import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
@@ -335,6 +336,10 @@ class EntryEditActivity : LockingActivity(),
|
||||
Log.e(TAG, "Unable to retrieve entry after database action", e)
|
||||
}
|
||||
}
|
||||
ACTION_DATABASE_RELOAD_TASK -> {
|
||||
// Close the current activity
|
||||
finish()
|
||||
}
|
||||
}
|
||||
coordinatorLayout?.showActionError(result)
|
||||
}
|
||||
@@ -361,7 +366,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))
|
||||
}
|
||||
}
|
||||
@@ -479,8 +484,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,13 +619,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
val inflater = menuInflater
|
||||
inflater.inflate(R.menu.database, menu)
|
||||
// Save database not needed here
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||
|
||||
MenuUtil.contributionMenuInflater(menuInflater, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -673,9 +676,6 @@ class EntryEditActivity : LockingActivity(),
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_save_database -> {
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
|
||||
}
|
||||
R.id.menu_contribute -> {
|
||||
MenuUtil.onContributionItemSelected(this)
|
||||
return true
|
||||
@@ -909,7 +909,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)) {
|
||||
@@ -917,7 +917,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
intent.putExtra(KEY_PARENT, group.nodeId)
|
||||
AutofillHelper.startActivityForAutofillResult(activity,
|
||||
intent,
|
||||
assistStructure,
|
||||
autofillComponent,
|
||||
searchInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -48,6 +47,7 @@ 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
|
||||
@@ -501,11 +501,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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -52,6 +51,7 @@ 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
|
||||
@@ -70,6 +70,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
|
||||
@@ -228,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 {
|
||||
@@ -317,7 +318,12 @@ class GroupActivity : LockingActivity(),
|
||||
if (result.isSuccess) {
|
||||
|
||||
// Rebuild all the list to avoid bug when delete node from sort
|
||||
mListNodesFragment?.rebuildList()
|
||||
try {
|
||||
mListNodesFragment?.rebuildList()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to rebuild the list after deletion")
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
// Add trash in views list if it doesn't exists
|
||||
if (database.isRecycleBinEnabled) {
|
||||
@@ -337,6 +343,12 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
ACTION_DATABASE_RELOAD_TASK -> {
|
||||
// Reload the current activity
|
||||
startActivity(intent)
|
||||
finish()
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
}
|
||||
}
|
||||
|
||||
coordinatorLayout?.showActionError(result)
|
||||
@@ -660,7 +672,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))
|
||||
}
|
||||
}
|
||||
@@ -873,6 +885,8 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
if (mSpecialMode == SpecialMode.DEFAULT) {
|
||||
MenuUtil.defaultMenuInflater(inflater, menu)
|
||||
} else {
|
||||
menu.findItem(R.id.menu_reload_database)?.isVisible = false
|
||||
}
|
||||
|
||||
// Menu for recycle bin
|
||||
@@ -998,6 +1012,10 @@ class GroupActivity : LockingActivity(),
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
|
||||
return true
|
||||
}
|
||||
R.id.menu_reload_database -> {
|
||||
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
|
||||
return true
|
||||
}
|
||||
R.id.menu_empty_recycle_bin -> {
|
||||
mCurrentGroup?.getChildren()?.let { listChildren ->
|
||||
// Automatically delete all elements
|
||||
@@ -1125,7 +1143,16 @@ class GroupActivity : LockingActivity(),
|
||||
private fun rebuildListNodes() {
|
||||
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment?
|
||||
// to refresh fragment
|
||||
mListNodesFragment?.rebuildList()
|
||||
try {
|
||||
mListNodesFragment?.rebuildList()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
coordinatorLayout?.let { coordinatorLayout ->
|
||||
Snackbar.make(coordinatorLayout,
|
||||
R.string.error_rebuild_list,
|
||||
Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
mCurrentGroup = mListNodesFragment?.mainGroup
|
||||
// Remove search in intent
|
||||
deletePreviousSearchGroup()
|
||||
@@ -1296,14 +1323,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)
|
||||
}
|
||||
}
|
||||
@@ -1420,21 +1447,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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -49,6 +48,7 @@ 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.AdvancedUnlockFragment
|
||||
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
||||
@@ -588,13 +588,13 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
|
||||
// Check permission
|
||||
private fun checkPermission() {
|
||||
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
val permissions = arrayOf(writePermission)
|
||||
if (Build.VERSION.SDK_INT >= 23
|
||||
if (Build.VERSION.SDK_INT in 23..28
|
||||
&& !readOnly
|
||||
&& !mPermissionAsked) {
|
||||
mPermissionAsked = true
|
||||
// Check self permission to show or not the dialog
|
||||
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
val permissions = arrayOf(writePermission)
|
||||
if (toolbar != null
|
||||
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
|
||||
@@ -720,7 +720,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
when (resultCode) {
|
||||
LockingActivity.RESULT_EXIT_LOCK -> {
|
||||
clearCredentialsViews()
|
||||
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
|
||||
Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
|
||||
}
|
||||
Activity.RESULT_CANCELED -> {
|
||||
clearCredentialsViews()
|
||||
@@ -838,13 +838,13 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -902,11 +902,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
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 {
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class App : MultiDexApplication() {
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
|
||||
Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
|
||||
super.onTerminate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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?)
|
||||
@@ -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,13 @@ 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<AssistStructure?>(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure ->
|
||||
return AutofillComponent(assistStructure,
|
||||
intent.getParcelableExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST))
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -68,26 +82,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 +98,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 +112,116 @@ 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<EntryInfo>,
|
||||
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<EntryInfo>().apply { add(entryInfo) })
|
||||
fun buildResponseAndSetResult(activity: Activity, entryInfo: EntryInfo) {
|
||||
buildResponseAndSetResult(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Autofill response for many entry
|
||||
*/
|
||||
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) {
|
||||
fun buildResponseAndSetResult(activity: Activity, entriesInfo: List<EntryInfo>) {
|
||||
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<AssistStructure>(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<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { structure ->
|
||||
StructureParser(structure).parse()?.let { result ->
|
||||
// New Response
|
||||
val inlineSuggestionsRequest = activity.intent?.getParcelableExtra<InlineSuggestionsRequest?>(EXTRA_INLINE_SUGGESTIONS_REQUEST)
|
||||
val response = buildResponse(activity, entriesInfo, result, inlineSuggestionsRequest)
|
||||
if (inlineSuggestionsRequest != null) {
|
||||
Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
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 +231,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 +274,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String>? = null
|
||||
var webDomainBlocklist: Set<String>? = 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,8 +172,45 @@ class KeeAutofillService : AutofillService() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Build inline presentation
|
||||
var inlinePresentation: InlinePresentation? = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& autofillInlineSuggestionsEnabled) {
|
||||
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)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation)
|
||||
} else {
|
||||
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
|
||||
}
|
||||
callback.onSuccess(responseBuilder.build())
|
||||
}
|
||||
}
|
||||
@@ -190,6 +250,7 @@ class KeeAutofillService : AutofillService() {
|
||||
|
||||
override fun onConnected() {
|
||||
Log.d(TAG, "onConnected")
|
||||
getPreferences()
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -117,10 +117,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
// To wait resume
|
||||
activityResult = ActivityResult(requestCode, resultCode, data)
|
||||
if (keepConnection) {
|
||||
activityResult = ActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
keepConnection = false
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -237,7 +237,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
Mode.WAIT_CREDENTIAL
|
||||
})
|
||||
}
|
||||
} ?: throw IODatabaseException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
if (allowOpenBiometricPrompt) {
|
||||
if (cryptoPrompt.isDeviceCredentialOperation)
|
||||
keepConnection = true
|
||||
@@ -329,7 +329,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
}
|
||||
} ?: throw Exception("AdvancedUnlockHelper not initialized")
|
||||
} ?: throw Exception("AdvancedUnlockManager not initialized")
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
@@ -358,21 +358,25 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: throw IODatabaseException()
|
||||
} ?: throw Exception("AdvancedUnlockHelper not initialized")
|
||||
} ?: throw Exception("AdvancedUnlockManager not initialized")
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun initAdvancedUnlockMode() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mAllowAdvancedUnlockMenu = false
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
|
||||
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
|
||||
Mode.WAIT_CREDENTIAL -> initWaitData()
|
||||
Mode.STORE_CREDENTIAL -> initEncryptData()
|
||||
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||
try {
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
|
||||
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
|
||||
Mode.WAIT_CREDENTIAL -> initWaitData()
|
||||
Mode.STORE_CREDENTIAL -> initEncryptData()
|
||||
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
onGenericException(e)
|
||||
}
|
||||
invalidateBiometricMenu()
|
||||
}
|
||||
@@ -388,7 +392,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
|
||||
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
mAddBiometricMenuInProgress = false
|
||||
requireActivity().invalidateOptionsMenu()
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,7 +446,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||
setAdvancedUnlockedMessageView(errString.toString())
|
||||
}
|
||||
@@ -450,7 +454,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onAuthenticationFailed() {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
||||
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
|
||||
}
|
||||
@@ -458,7 +462,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onAuthenticationSucceeded() {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> {
|
||||
}
|
||||
@@ -485,7 +489,9 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
advancedUnlockManager?.decryptData(value)
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: throw IODatabaseException()
|
||||
} ?: run {
|
||||
onAuthenticationError(-1, getString(R.string.error_database_uri_null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +521,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
}
|
||||
|
||||
private fun showViews(show: Boolean) {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.visibility = if (show)
|
||||
View.VISIBLE
|
||||
else {
|
||||
@@ -526,20 +532,20 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun setAdvancedUnlockedTitleView(textId: Int) {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.setTitle(textId)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun setAdvancedUnlockedMessageView(textId: Int) {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.setMessage(textId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
|
||||
requireActivity().runOnUiThread {
|
||||
activity?.runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.message = text
|
||||
}
|
||||
}
|
||||
@@ -548,18 +554,20 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
readOnlyEducationPerformed: Boolean,
|
||||
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
|
||||
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !readOnlyEducationPerformed) {
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
|
||||
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
|
||||
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
|
||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
|
||||
onEducationViewClick,
|
||||
onOuterViewClick)
|
||||
}
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !readOnlyEducationPerformed) {
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
|
||||
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
|
||||
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
|
||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
|
||||
onEducationViewClick,
|
||||
onOuterViewClick)
|
||||
}
|
||||
} catch (ignored: Exception) {}
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.utils.closeDatabase
|
||||
|
||||
class CreateDatabaseRunnable(context: Context,
|
||||
private val mDatabase: Database,
|
||||
@@ -47,7 +46,7 @@ class CreateDatabaseRunnable(context: Context,
|
||||
createData(mDatabaseUri, databaseName, rootName)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
|
||||
mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
|
||||
setError(e)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.utils.closeDatabase
|
||||
|
||||
class LoadDatabaseRunnable(private val context: Context,
|
||||
private val mDatabase: Database,
|
||||
@@ -47,7 +46,7 @@ class LoadDatabaseRunnable(private val context: Context,
|
||||
|
||||
override fun onStartRun() {
|
||||
// Clear before we load
|
||||
mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
|
||||
mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
|
||||
}
|
||||
|
||||
override fun onActionRun() {
|
||||
@@ -59,9 +58,6 @@ class LoadDatabaseRunnable(private val context: Context,
|
||||
mFixDuplicateUUID,
|
||||
progressTaskUpdater)
|
||||
}
|
||||
catch (e: DuplicateUuidDatabaseException) {
|
||||
setError(e)
|
||||
}
|
||||
catch (e: LoadDatabaseException) {
|
||||
setError(e)
|
||||
}
|
||||
@@ -83,7 +79,7 @@ class LoadDatabaseRunnable(private val context: Context,
|
||||
// Register the current time to init the lock timer
|
||||
PreferencesUtil.saveCurrentTime(context)
|
||||
} else {
|
||||
mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
|
||||
mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
@@ -35,6 +37,7 @@ import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
||||
@@ -44,6 +47,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||
@@ -84,6 +88,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
private var serviceConnection: ServiceConnection? = null
|
||||
|
||||
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
|
||||
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
|
||||
|
||||
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
||||
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||
@@ -101,6 +106,28 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
}
|
||||
}
|
||||
|
||||
private val mActionDatabaseListener = object: DatabaseChangedDialogFragment.ActionDatabaseChangedListener {
|
||||
override fun validateDatabaseChanged() {
|
||||
mBinder?.getService()?.saveDatabaseInfo()
|
||||
}
|
||||
}
|
||||
|
||||
private var databaseInfoListener = object: DatabaseTaskNotificationService.DatabaseInfoListener {
|
||||
override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
|
||||
newDatabaseInfo: SnapFileDatabaseInfo) {
|
||||
if (databaseChangedDialogFragment == null) {
|
||||
databaseChangedDialogFragment = activity.supportFragmentManager
|
||||
.findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
|
||||
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
|
||||
}
|
||||
if (progressTaskDialogFragment == null) {
|
||||
databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(previousDatabaseInfo, newDatabaseInfo)
|
||||
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
|
||||
databaseChangedDialogFragment?.show(activity.supportFragmentManager, DATABASE_CHANGED_DIALOG_TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDialog(titleId: Int? = null,
|
||||
messageId: Int? = null,
|
||||
warningId: Int? = null) {
|
||||
@@ -140,11 +167,14 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
|
||||
addActionTaskListener(actionTaskListener)
|
||||
addDatabaseFileInfoListener(databaseInfoListener)
|
||||
getService().checkAction()
|
||||
getService().checkDatabaseInfo()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
|
||||
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||
mBinder = null
|
||||
}
|
||||
@@ -206,6 +236,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
fun unregisterProgressTask() {
|
||||
stopDialog()
|
||||
|
||||
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
|
||||
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||
mBinder = null
|
||||
|
||||
@@ -264,6 +295,13 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
, ACTION_DATABASE_LOAD_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseReload(fixDuplicateUuid: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
|
||||
}
|
||||
, ACTION_DATABASE_RELOAD_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseAssignPassword(databaseUri: Uri,
|
||||
masterPasswordChecked: Boolean,
|
||||
masterPassword: String?,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action
|
||||
|
||||
import android.content.Context
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class ReloadDatabaseRunnable(private val context: Context,
|
||||
private val mDatabase: Database,
|
||||
private val progressTaskUpdater: ProgressTaskUpdater?,
|
||||
private val mLoadDatabaseResult: ((Result) -> Unit)?)
|
||||
: ActionRunnable() {
|
||||
|
||||
override fun onStartRun() {
|
||||
// Clear before we load
|
||||
mDatabase.clear(UriUtil.getBinaryDir(context))
|
||||
}
|
||||
|
||||
override fun onActionRun() {
|
||||
try {
|
||||
mDatabase.reloadData(context.contentResolver,
|
||||
UriUtil.getBinaryDir(context),
|
||||
progressTaskUpdater)
|
||||
}
|
||||
catch (e: LoadDatabaseException) {
|
||||
setError(e)
|
||||
}
|
||||
|
||||
if (result.isSuccess) {
|
||||
// Register the current time to init the lock timer
|
||||
PreferencesUtil.saveCurrentTime(context)
|
||||
} else {
|
||||
mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishRun() {
|
||||
mLoadDatabaseResult?.invoke(result)
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.*
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
|
||||
@@ -330,29 +327,11 @@ class Database {
|
||||
}
|
||||
|
||||
@Throws(LoadDatabaseException::class)
|
||||
fun loadData(uri: Uri, password: String?, keyfile: Uri?,
|
||||
readOnly: Boolean,
|
||||
contentResolver: ContentResolver,
|
||||
cacheDirectory: File,
|
||||
fixDuplicateUUID: Boolean,
|
||||
progressTaskUpdater: ProgressTaskUpdater?) {
|
||||
|
||||
this.fileUri = uri
|
||||
isReadOnly = readOnly
|
||||
if (uri.scheme == "file") {
|
||||
val file = File(uri.path!!)
|
||||
isReadOnly = !file.canWrite()
|
||||
}
|
||||
|
||||
// Pass KeyFile Uri as InputStreams
|
||||
private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
|
||||
openDatabaseKDB: (InputStream) -> DatabaseKDB,
|
||||
openDatabaseKDBX: (InputStream) -> DatabaseKDBX) {
|
||||
var databaseInputStream: InputStream? = null
|
||||
var keyFileInputStream: InputStream? = null
|
||||
try {
|
||||
// Get keyFile inputStream
|
||||
keyfile?.let {
|
||||
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
|
||||
}
|
||||
|
||||
// Load Data, pass Uris as InputStreams
|
||||
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
|
||||
?: throw IOException("Database input stream cannot be retrieve")
|
||||
@@ -374,22 +353,10 @@ class Database {
|
||||
|
||||
when {
|
||||
// Header of database KDB
|
||||
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB(
|
||||
cacheDirectory,
|
||||
fixDuplicateUUID)
|
||||
.openDatabase(databaseInputStream,
|
||||
password,
|
||||
keyFileInputStream,
|
||||
progressTaskUpdater))
|
||||
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(openDatabaseKDB(databaseInputStream))
|
||||
|
||||
// Header of database KDBX
|
||||
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX(
|
||||
cacheDirectory,
|
||||
fixDuplicateUUID)
|
||||
.openDatabase(databaseInputStream,
|
||||
password,
|
||||
keyFileInputStream,
|
||||
progressTaskUpdater))
|
||||
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(openDatabaseKDBX(databaseInputStream))
|
||||
|
||||
// Header not recognized
|
||||
else -> throw SignatureDatabaseException()
|
||||
@@ -397,14 +364,90 @@ class Database {
|
||||
|
||||
this.mSearchHelper = SearchHelper()
|
||||
loaded = true
|
||||
} catch (e: LoadDatabaseException) {
|
||||
throw e
|
||||
} finally {
|
||||
databaseInputStream?.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(LoadDatabaseException::class)
|
||||
fun loadData(uri: Uri, password: String?, keyfile: Uri?,
|
||||
readOnly: Boolean,
|
||||
contentResolver: ContentResolver,
|
||||
cacheDirectory: File,
|
||||
fixDuplicateUUID: Boolean,
|
||||
progressTaskUpdater: ProgressTaskUpdater?) {
|
||||
|
||||
// Save database URI
|
||||
this.fileUri = uri
|
||||
|
||||
// Check if the file is writable
|
||||
this.isReadOnly = readOnly
|
||||
|
||||
// Pass KeyFile Uri as InputStreams
|
||||
var keyFileInputStream: InputStream? = null
|
||||
try {
|
||||
// Get keyFile inputStream
|
||||
keyfile?.let {
|
||||
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
|
||||
}
|
||||
|
||||
// Read database stream for the first time
|
||||
readDatabaseStream(contentResolver, uri,
|
||||
{ databaseInputStream ->
|
||||
DatabaseInputKDB(cacheDirectory)
|
||||
.openDatabase(databaseInputStream,
|
||||
password,
|
||||
keyFileInputStream,
|
||||
progressTaskUpdater,
|
||||
fixDuplicateUUID)
|
||||
},
|
||||
{ databaseInputStream ->
|
||||
DatabaseInputKDBX(cacheDirectory)
|
||||
.openDatabase(databaseInputStream,
|
||||
password,
|
||||
keyFileInputStream,
|
||||
progressTaskUpdater,
|
||||
fixDuplicateUUID)
|
||||
}
|
||||
)
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.e(TAG, "Unable to load keyfile", e)
|
||||
throw FileNotFoundDatabaseException()
|
||||
} catch (e: LoadDatabaseException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
throw FileNotFoundDatabaseException()
|
||||
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
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,7 +469,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 +574,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 +587,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
|
||||
|
||||
@@ -426,6 +426,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
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<Group> {
|
||||
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<Group> {
|
||||
database?.binaryPool?.let { binaryPool ->
|
||||
addAttachments(binaryPool, newEntryInfo.attachments)
|
||||
}
|
||||
// Update date time
|
||||
lastAccessTime = DateInstant()
|
||||
lastModificationTime = DateInstant()
|
||||
|
||||
database?.stopManageEntry(this)
|
||||
}
|
||||
|
||||
@@ -163,10 +163,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
finalKey = messageDigest.digest()
|
||||
}
|
||||
|
||||
override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun createGroup(): GroupKDB {
|
||||
return GroupKDB()
|
||||
}
|
||||
|
||||
@@ -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.hexStringToByteArray
|
||||
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.w3c.dom.Node
|
||||
import org.w3c.dom.Text
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -180,7 +182,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
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()) {
|
||||
@@ -198,7 +201,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
CompressionAlgorithm.None -> {
|
||||
decompressAllBinaries()
|
||||
}
|
||||
CompressionAlgorithm.GZip -> {}
|
||||
CompressionAlgorithm.GZip -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,36 +382,79 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
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
|
||||
// <KeyFile> 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)
|
||||
// <Meta>
|
||||
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)
|
||||
// <Version>
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// <Key>
|
||||
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)
|
||||
// <Data>
|
||||
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
|
||||
}
|
||||
2F -> {
|
||||
if (hashString != null
|
||||
&& checkKeyFileHash(dataString, hashString))
|
||||
Log.i(TAG, "Successful key file hash check.")
|
||||
else
|
||||
Log.e(TAG, "Unable to check the hash of the key file.")
|
||||
}
|
||||
}
|
||||
return Base64.decode(dataString, BASE_64_FLAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,10 +464,26 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
} 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(data.hexStringToByteArray())
|
||||
.copyOfRange(0, 4)
|
||||
.toHexString()
|
||||
success = dataDigest == hash
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
override fun newGroupId(): NodeIdUUID {
|
||||
var newId: NodeIdUUID
|
||||
do {
|
||||
@@ -634,11 +697,12 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
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
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@ 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 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 +127,30 @@ 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 32 bits key file
|
||||
if (keyData.size == 32) {
|
||||
return keyData
|
||||
}
|
||||
|
||||
when (keyData.size.toLong()) {
|
||||
32L -> return keyData
|
||||
64L -> try {
|
||||
return hexStringToByteArray(String(keyData))
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
// Key is not base 64, treat it as binary data
|
||||
}
|
||||
// Check XML key file
|
||||
val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
|
||||
if (xmlKeyByteArray != null) {
|
||||
return xmlKeyByteArray
|
||||
}
|
||||
|
||||
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 +382,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,13 @@ abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<DatabaseKDB>(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 {
|
||||
|
||||
@@ -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<DatabaseKDBX>(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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.file.output
|
||||
|
||||
open class DatabaseHeaderOutput {
|
||||
var hashOfHeader: ByteArray? = null
|
||||
protected set
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import java.io.OutputStream
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
|
||||
abstract class DatabaseOutput<Header : DatabaseHeader> protected constructor(protected var mOS: OutputStream) {
|
||||
abstract class DatabaseOutput<Header : DatabaseHeader> protected constructor(protected var mOutputStream: OutputStream) {
|
||||
|
||||
@Throws(DatabaseOutputException::class)
|
||||
protected open fun setIVs(header: Header): SecureRandom {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,13 +581,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
|
||||
if (protect) {
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
||||
|
||||
val data = value.toString().toByteArray(charset("UTF-8"))
|
||||
val valLength = data.size
|
||||
|
||||
if (valLength > 0) {
|
||||
val encoded = ByteArray(valLength)
|
||||
randomStream!!.processBytes(data, 0, valLength, encoded, 0)
|
||||
val data = value.toString().toByteArray()
|
||||
val dataLength = data.size
|
||||
if (data.isNotEmpty()) {
|
||||
val encoded = ByteArray(dataLength)
|
||||
randomStream!!.processBytes(data, 0, dataLength, encoded, 0)
|
||||
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -95,10 +101,6 @@ class EntryInfo : Parcelable {
|
||||
return customFields.lastOrNull { it.name == label } != null
|
||||
}
|
||||
|
||||
fun isAutoGeneratedField(field: Field): Boolean {
|
||||
return field.name == OTP_TOKEN_FIELD
|
||||
}
|
||||
|
||||
fun getGeneratedFieldValue(label: String): String {
|
||||
if (label == OTP_TOKEN_FIELD) {
|
||||
otpModel?.let {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.kunzisoft.keepass.model
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.text.format.Formatter
|
||||
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Utility data class to get FileDatabaseInfo at a `t` time
|
||||
*/
|
||||
data class SnapFileDatabaseInfo(var fileUri: Uri?,
|
||||
var exists: Boolean,
|
||||
var lastModification: Long?,
|
||||
var size: Long?): Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readParcelable(Uri::class.java.classLoader),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readValue(Long::class.java.classLoader) as? Long,
|
||||
parcel.readValue(Long::class.java.classLoader) as? Long) {
|
||||
}
|
||||
|
||||
fun toString(context: Context): String {
|
||||
val lastModificationString = DateFormat.getDateTimeInstance()
|
||||
.format(Date(lastModification ?: 0))
|
||||
return "$lastModificationString, " +
|
||||
Formatter.formatFileSize(context, size ?: 0)
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeParcelable(fileUri, flags)
|
||||
parcel.writeByte(if (exists) 1 else 0)
|
||||
parcel.writeValue(lastModification)
|
||||
parcel.writeValue(size)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<SnapFileDatabaseInfo> {
|
||||
override fun createFromParcel(parcel: Parcel): SnapFileDatabaseInfo {
|
||||
return SnapFileDatabaseInfo(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<SnapFileDatabaseInfo?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
|
||||
fun fromFileDatabaseInfo(fileDatabaseInfo: FileDatabaseInfo): SnapFileDatabaseInfo {
|
||||
return SnapFileDatabaseInfo(
|
||||
fileDatabaseInfo.fileUri,
|
||||
fileDatabaseInfo.exists,
|
||||
fileDatabaseInfo.getLastModification(),
|
||||
fileDatabaseInfo.getSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ 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<NotificationFieldId>
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -22,9 +22,8 @@ package com.kunzisoft.keepass.notifications
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
@@ -40,6 +39,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
@@ -47,6 +47,7 @@ import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
||||
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||
import com.kunzisoft.keepass.utils.closeDatabase
|
||||
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@@ -65,6 +66,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
private var mAllowFinishAction = AtomicBoolean()
|
||||
private var mActionRunning = false
|
||||
|
||||
private var mSnapFileDatabaseInfo: SnapFileDatabaseInfo? = null
|
||||
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
|
||||
|
||||
private var mIconId: Int = R.drawable.notification_ic_database_load
|
||||
private var mTitleId: Int = R.string.database_opened
|
||||
private var mMessageId: Int? = null
|
||||
@@ -93,6 +97,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
mAllowFinishAction.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
|
||||
mDatabaseInfoListeners.add(databaseInfoListener)
|
||||
}
|
||||
|
||||
fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
|
||||
mDatabaseInfoListeners.remove(databaseInfoListener)
|
||||
}
|
||||
}
|
||||
|
||||
interface ActionTaskListener {
|
||||
@@ -101,6 +113,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
fun onStopAction(actionTask: String, result: ActionRunnable.Result)
|
||||
}
|
||||
|
||||
interface DatabaseInfoListener {
|
||||
fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
|
||||
newDatabaseInfo: SnapFileDatabaseInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Force to call [ActionTaskListener.onStartAction] if the action is still running
|
||||
*/
|
||||
@@ -112,6 +129,31 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
fun checkDatabaseInfo() {
|
||||
mDatabase.fileUri?.let {
|
||||
val previousDatabaseInfo = mSnapFileDatabaseInfo
|
||||
val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||
FileDatabaseInfo(applicationContext, it))
|
||||
if (previousDatabaseInfo != null) {
|
||||
if (previousDatabaseInfo != lastFileDatabaseInfo) {
|
||||
Log.i(TAG, "Database file modified " +
|
||||
"$previousDatabaseInfo != $lastFileDatabaseInfo ")
|
||||
// Call listener to indicate a change in database info
|
||||
mDatabaseInfoListeners.forEach { listener ->
|
||||
listener.onDatabaseInfoChanged(previousDatabaseInfo, lastFileDatabaseInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveDatabaseInfo() {
|
||||
mDatabase.fileUri?.let {
|
||||
mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||
FileDatabaseInfo(applicationContext, it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return mActionTaskBinder
|
||||
@@ -138,6 +180,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
val actionRunnable: ActionRunnable? = when (intentAction) {
|
||||
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
|
||||
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
|
||||
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask()
|
||||
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
|
||||
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
|
||||
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
|
||||
@@ -193,6 +236,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
} finally {
|
||||
removeIntentData(intent)
|
||||
// Save the database info after performing action
|
||||
saveDatabaseInfo()
|
||||
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
|
||||
if (!mDatabase.loaded) {
|
||||
@@ -214,7 +259,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
|
||||
return when (intentAction) {
|
||||
ACTION_DATABASE_LOAD_TASK, null -> {
|
||||
ACTION_DATABASE_LOAD_TASK,
|
||||
ACTION_DATABASE_RELOAD_TASK,
|
||||
null -> {
|
||||
START_STICKY
|
||||
}
|
||||
else -> {
|
||||
@@ -248,7 +295,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
else -> {
|
||||
when (intentAction) {
|
||||
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
|
||||
ACTION_DATABASE_LOAD_TASK -> R.string.loading_database
|
||||
ACTION_DATABASE_LOAD_TASK,
|
||||
ACTION_DATABASE_RELOAD_TASK -> R.string.loading_database
|
||||
ACTION_DATABASE_SAVE -> R.string.saving_database
|
||||
else -> {
|
||||
R.string.command_execution
|
||||
@@ -258,13 +306,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
|
||||
mMessageId = when (intentAction) {
|
||||
ACTION_DATABASE_LOAD_TASK -> null
|
||||
ACTION_DATABASE_LOAD_TASK,
|
||||
ACTION_DATABASE_RELOAD_TASK -> null
|
||||
else -> null
|
||||
}
|
||||
|
||||
mWarningId =
|
||||
if (!saveAction
|
||||
|| intentAction == ACTION_DATABASE_LOAD_TASK)
|
||||
|| intentAction == ACTION_DATABASE_LOAD_TASK
|
||||
|| intentAction == ACTION_DATABASE_RELOAD_TASK)
|
||||
null
|
||||
else
|
||||
R.string.do_not_kill_app
|
||||
@@ -342,9 +392,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
* Execute action with a coroutine
|
||||
*/
|
||||
private suspend fun executeAction(progressTaskUpdater: ProgressTaskUpdater,
|
||||
onPreExecute: () -> Unit,
|
||||
onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
|
||||
onPostExecute: (result: ActionRunnable.Result) -> Unit) {
|
||||
onPreExecute: () -> Unit,
|
||||
onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
|
||||
onPostExecute: (result: ActionRunnable.Result) -> Unit) {
|
||||
mAllowFinishAction.set(false)
|
||||
|
||||
TimeoutHelper.temporarilyDisableTimeout()
|
||||
@@ -465,6 +515,17 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseReloadActionTask(): ActionRunnable {
|
||||
return ReloadDatabaseRunnable(
|
||||
this,
|
||||
mDatabase,
|
||||
this
|
||||
) { result ->
|
||||
// No need to add each info to reload database
|
||||
result.data = Bundle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? {
|
||||
return if (intent.hasExtra(DATABASE_URI_KEY)
|
||||
&& intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY)
|
||||
@@ -770,6 +831,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|
||||
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
|
||||
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK"
|
||||
const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
|
||||
const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
|
||||
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
|
||||
const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK"
|
||||
|
||||
@@ -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
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -316,7 +365,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 +431,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
|
||||
|
||||
@@ -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?) {
|
||||
|
||||
@@ -103,6 +103,6 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen)
|
||||
fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean = false)
|
||||
}
|
||||
}
|
||||
@@ -552,6 +552,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
|
||||
true
|
||||
}
|
||||
R.id.menu_reload_database -> {
|
||||
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseReload(false)
|
||||
return true
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Check the time lock before launching settings
|
||||
|
||||
@@ -436,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),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.backup.BackupManager
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
@@ -37,6 +36,7 @@ import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.view.showActionError
|
||||
|
||||
@@ -95,12 +95,28 @@ open class SettingsActivity
|
||||
backupManager = BackupManager(this)
|
||||
|
||||
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
|
||||
// Call result in fragment
|
||||
(supportFragmentManager
|
||||
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
|
||||
?.onProgressDialogThreadResult(actionTask, result)
|
||||
when (actionTask) {
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
|
||||
// Reload the current activity
|
||||
startActivity(intent)
|
||||
finish()
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
}
|
||||
else -> {
|
||||
// Call result in fragment
|
||||
(supportFragmentManager
|
||||
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
|
||||
?.onProgressDialogThreadResult(actionTask, result)
|
||||
coordinatorLayout?.showActionError(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coordinatorLayout?.showActionError(result)
|
||||
// To reload the current screen
|
||||
if (intent.extras?.containsKey(FRAGMENT_ARG) == true) {
|
||||
intent.extras?.getString(FRAGMENT_ARG)?.let { fragmentScreenName ->
|
||||
onNestedPreferenceSelected(NestedSettingsFragment.Screen.valueOf(fragmentScreenName), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,25 +209,33 @@ open class SettingsActivity
|
||||
hideOrShowLockButton(NestedSettingsFragment.Screen.APPLICATION)
|
||||
}
|
||||
|
||||
private fun replaceFragment(key: NestedSettingsFragment.Screen) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||
private fun replaceFragment(key: NestedSettingsFragment.Screen, reload: Boolean) {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
if (reload) {
|
||||
setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out,
|
||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
||||
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
|
||||
.addToBackStack(TAG_NESTED)
|
||||
.commit()
|
||||
} else {
|
||||
setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
||||
}
|
||||
replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
|
||||
addToBackStack(TAG_NESTED)
|
||||
commit()
|
||||
}
|
||||
|
||||
toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key)
|
||||
// To reload the current screen
|
||||
intent.putExtra(FRAGMENT_ARG, key.name)
|
||||
hideOrShowLockButton(key)
|
||||
}
|
||||
|
||||
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen) {
|
||||
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
|
||||
if (mTimeoutEnable)
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
||||
replaceFragment(key)
|
||||
replaceFragment(key, reload)
|
||||
}
|
||||
else
|
||||
replaceFragment(key)
|
||||
replaceFragment(key, reload)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -226,6 +250,7 @@ open class SettingsActivity
|
||||
private const val SHOW_LOCK = "SHOW_LOCK"
|
||||
private const val TITLE_KEY = "TITLE_KEY"
|
||||
private const val TAG_NESTED = "TAG_NESTED"
|
||||
private const val FRAGMENT_ARG = "FRAGMENT_ARG"
|
||||
|
||||
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
|
||||
val intent = Intent(activity, SettingsActivity::class.java)
|
||||
|
||||
@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
|
||||
cancelAll()
|
||||
}
|
||||
// Clear data
|
||||
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
|
||||
Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
|
||||
}
|
||||
26
app/src/main/java/com/kunzisoft/keepass/utils/StringUtil.kt
Normal file
26
app/src/main/java/com/kunzisoft/keepass/utils/StringUtil.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
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 String.hexStringToByteArray(): ByteArray {
|
||||
val len = this.length
|
||||
val data = ByteArray(len / 2)
|
||||
var i = 0
|
||||
while (i < len) {
|
||||
data[i / 2] = ((Character.digit(this[i], 16) shl 4)
|
||||
+ Character.digit(this[i + 1], 16)).toByte()
|
||||
i += 2
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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?) {
|
||||
|
||||
@@ -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())
|
||||
|
||||
7
app/src/main/res/drawable/ic_reload_white_24dp.xml
Normal file
7
app/src/main/res/drawable/ic_reload_white_24dp.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24" >
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
|
||||
</vector>
|
||||
@@ -46,9 +46,9 @@
|
||||
android:id="@+id/entry_extra_field_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignTop="@+id/entry_extra_field_value_container"
|
||||
android:src="@drawable/ic_more_white_24dp"
|
||||
android:contentDescription="@string/menu_edit"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"/>
|
||||
|
||||
@@ -175,12 +175,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/entry_accessed"
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:visibility="gone"
|
||||
android:id="@+id/entry_accessed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
|
||||
|
||||
<!-- Expires -->
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/keyfile_open_button">
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/keyfile_name"
|
||||
@@ -33,16 +33,4 @@
|
||||
android:imeOptions="actionDone"
|
||||
android:maxLines="1"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/keyfile_open_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:contentDescription="@string/content_description_open_file"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/background_item_selection"
|
||||
android:src="@drawable/ic_folder_white_24dp"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -22,6 +22,6 @@
|
||||
<item android:id="@+id/menu_contribute"
|
||||
android:icon="@drawable/ic_heart_white_24dp"
|
||||
android:title="@string/contribute"
|
||||
android:orderInCategory="95"
|
||||
android:orderInCategory="99"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -24,4 +24,9 @@
|
||||
android:title="@string/menu_save_database"
|
||||
android:orderInCategory="95"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item android:id="@+id/menu_reload_database"
|
||||
android:icon="@drawable/ic_reload_white_24dp"
|
||||
android:title="@string/menu_reload_database"
|
||||
android:orderInCategory="96"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
12
app/src/main/res/values-bg/strings.xml
Normal file
12
app/src/main/res/values-bg/strings.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="feedback">Обратна връзка</string>
|
||||
<string name="encryption_algorithm">Алгоритъм за криптиране</string>
|
||||
<string name="encryption">Криптиране</string>
|
||||
<string name="security">Сигурност</string>
|
||||
<string name="master_key">Главен ключ</string>
|
||||
<string name="add_group">Добави група</string>
|
||||
<string name="edit_entry">Редактирай</string>
|
||||
<string name="add_entry">Добави</string>
|
||||
<string name="accept">Приемам</string>
|
||||
</resources>
|
||||
@@ -19,12 +19,12 @@
|
||||
Czech translation by Jan Vaněk
|
||||
--><resources>
|
||||
<string name="homepage">Domovská stránka</string>
|
||||
<string name="about_description">Androidová verze správce hesel KeePass</string>
|
||||
<string name="about_description">Implementace správce hesel KeePass pro Android</string>
|
||||
<string name="accept">Přijmout</string>
|
||||
<string name="add_entry">Přidat záznam</string>
|
||||
<string name="add_group">Přidat skupinu</string>
|
||||
<string name="encryption_algorithm">Šifrovací algoritmus</string>
|
||||
<string name="app_timeout">Časový limit aplikace</string>
|
||||
<string name="app_timeout">Časový limit</string>
|
||||
<string name="app_timeout_summary">Doba nečinnosti, po které se aplikace zamkne</string>
|
||||
<string name="application">Aplikace</string>
|
||||
<string name="menu_app_settings">Nastavení aplikace</string>
|
||||
@@ -35,16 +35,16 @@
|
||||
<string name="clipboard_error">Některá zařízení nedovolují aplikacím používat schránku.</string>
|
||||
<string name="clipboard_error_clear">Nelze vyprázdnit schránku</string>
|
||||
<string name="clipboard_timeout">Časový limit schránky</string>
|
||||
<string name="clipboard_timeout_summary">Doba uchování ve schránce</string>
|
||||
<string name="clipboard_timeout_summary">Doba uchování ve schránce (je-li podporována zařízením)</string>
|
||||
<string name="select_to_copy">Vyberte zkopírovat %1$s do schránky</string>
|
||||
<string name="retrieving_db_key">Načítání klíče databáze…</string>
|
||||
<string name="retrieving_db_key">Načítám klíč databáze…</string>
|
||||
<string name="database">Databáze</string>
|
||||
<string name="decrypting_db">Dešifrování obsahu databáze…</string>
|
||||
<string name="decrypting_db">Dešifruji obsah databáze…</string>
|
||||
<string name="default_checkbox">Použít jako výchozí databázi</string>
|
||||
<string name="digits">Číslice</string>
|
||||
<string name="select_database_file">Otevřít existující databázi</string>
|
||||
<string name="entry_accessed">Poslední přístup</string>
|
||||
<string name="entry_cancel">Storno</string>
|
||||
<string name="entry_cancel">Zrušit</string>
|
||||
<string name="entry_notes">Poznámky</string>
|
||||
<string name="entry_confpassword">Potvrďte heslo</string>
|
||||
<string name="entry_created">Vytvořeno</string>
|
||||
@@ -55,27 +55,27 @@
|
||||
<string name="entry_password">Heslo</string>
|
||||
<string name="save">Uložit</string>
|
||||
<string name="entry_title">Název</string>
|
||||
<string name="entry_url">URL adresa</string>
|
||||
<string name="entry_url">URL</string>
|
||||
<string name="entry_user_name">Uživatelské jméno</string>
|
||||
<string name="error_arc4">Arcfour proudová šifra není podporována.</string>
|
||||
<string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string>
|
||||
<string name="error_file_not_create">Soubor se nedaří vytvořit</string>
|
||||
<string name="error_invalid_db">Databázi nelze číst.</string>
|
||||
<string name="error_file_not_create">Soubor se nepodařilo vytvořit</string>
|
||||
<string name="error_invalid_db">Databázi se nepodařilo načíst.</string>
|
||||
<string name="error_invalid_path">Ujistěte se, že cesta je správná.</string>
|
||||
<string name="error_no_name">Zadejte jméno.</string>
|
||||
<string name="error_nokeyfile">Vyberte soubor s klíčem.</string>
|
||||
<string name="error_out_of_memory">Nedostatek paměti k načtení celé databáze.</string>
|
||||
<string name="error_pass_gen_type">Je třeba zvolit alespoň jeden způsob vytváření hesla.</string>
|
||||
<string name="error_pass_match">Zadaná hesla se neshodují.</string>
|
||||
<string name="error_pass_match">Hesla se neshodují.</string>
|
||||
<string name="error_rounds_too_large">Příliš vysoký „Počet průchodů“. Nastavuji na 2147483648.</string>
|
||||
<string name="error_string_key">Je třeba, aby každý řetězec měl název kolonky.</string>
|
||||
<string name="error_wrong_length">Do pole „Délka“ zadejte celé kladné číslo.</string>
|
||||
<string name="field_name">Název pole</string>
|
||||
<string name="field_value">Hodnota pole</string>
|
||||
<string name="field_name">Název kolonky</string>
|
||||
<string name="field_value">Hodnota kolonky</string>
|
||||
<string name="file_browser">Správce souborů</string>
|
||||
<string name="generate_password">Vytvoř heslo</string>
|
||||
<string name="generate_password">Generovat heslo</string>
|
||||
<string name="hint_conf_pass">Potvrdit heslo</string>
|
||||
<string name="hint_generated_password">Vytvořené heslo</string>
|
||||
<string name="hint_generated_password">Generované heslo</string>
|
||||
<string name="hint_group_name">Název skupiny</string>
|
||||
<string name="hint_keyfile">Soubor s klíčem</string>
|
||||
<string name="hint_length">Délka</string>
|
||||
@@ -83,8 +83,8 @@
|
||||
<string name="password">Heslo</string>
|
||||
<string name="invalid_credentials">Nebylo možno načíst autentizační údaje.</string>
|
||||
<string name="invalid_algorithm">Nesprávný algoritmus.</string>
|
||||
<string name="invalid_db_sig">Nedaří se rozpoznat formát databáze.</string>
|
||||
<string name="keyfile_is_empty">Soubor s klíčem je prázdný.</string>
|
||||
<string name="invalid_db_sig">Nepodařilo se rozpoznat formát databáze.</string>
|
||||
<string name="keyfile_is_empty">Soubor klíče je prázdný.</string>
|
||||
<string name="length">Délka</string>
|
||||
<string name="list_size_title">Velikost položek seznamu</string>
|
||||
<string name="list_size_summary">Velikost textu v seznamu prvků</string>
|
||||
@@ -97,28 +97,28 @@
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="menu_database_settings">Nastavení databáze</string>
|
||||
<string name="menu_delete">Smazat</string>
|
||||
<string name="menu_donate">Podpořit vývoj darem</string>
|
||||
<string name="menu_donate">Přispět darem</string>
|
||||
<string name="menu_edit">Upravit</string>
|
||||
<string name="menu_hide_password">Skrýt heslo</string>
|
||||
<string name="menu_lock">Zamknout databázi</string>
|
||||
<string name="menu_open">Otevřít</string>
|
||||
<string name="menu_search">Hledat</string>
|
||||
<string name="menu_showpass">Ukaž heslo</string>
|
||||
<string name="menu_url">Jít na URL</string>
|
||||
<string name="menu_showpass">Ukázat heslo</string>
|
||||
<string name="menu_url">Přejít na URL</string>
|
||||
<string name="minus">Mínus</string>
|
||||
<string name="never">Nikdy</string>
|
||||
<string name="no_results">Žádné výsledky hledání</string>
|
||||
<string name="no_url_handler">Pro otevření tohoto URL nainstalujte webový prohlížeč.</string>
|
||||
<string name="omit_backup_search_title">Neprohledávat položky v záloze</string>
|
||||
<string name="omit_backup_search_summary">Vynechat skupiny „Záloha“ a \"Koš\" z výsledků vyhledávání</string>
|
||||
<string name="progress_create">Vytvářím novou databázi…</string>
|
||||
<string name="progress_title">Zpracování…</string>
|
||||
<string name="progress_create">Zakládám novou databázi…</string>
|
||||
<string name="progress_title">Pracuji…</string>
|
||||
<string name="protection">Ochrana</string>
|
||||
<string name="read_only_warning">Ke změně v databázi potřebuje KeePassDX oprávnění pro zápis.</string>
|
||||
<string name="read_only_warning">Ke změně v databáze potřebuje KeePassDX oprávnění pro zápis.</string>
|
||||
<string name="content_description_remove_from_list">Odstranit</string>
|
||||
<string name="encryption_rijndael">Rijndael (AES)</string>
|
||||
<string name="root">Kořen</string>
|
||||
<string name="rounds">Počet šifrovacích průchodů</string>
|
||||
<string name="rounds">Transformační průchody</string>
|
||||
<string name="rounds_explanation">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í.</string>
|
||||
<string name="saving_database">Ukládám databázi…</string>
|
||||
<string name="space">Místo</string>
|
||||
@@ -134,7 +134,7 @@
|
||||
<string name="version_label">Verze %1$s</string>
|
||||
<string name="education_unlock_summary">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.</string>
|
||||
\nNezapomeňte po každé úpravě zálohovat kopii svého .kdbx souboru na bezpečné místo.</string>
|
||||
<string-array name="timeout_options">
|
||||
<item>5 sekund</item>
|
||||
<item>10 sekund</item>
|
||||
@@ -155,41 +155,41 @@
|
||||
<string name="encryption">Šifrování</string>
|
||||
<string name="key_derivation_function">Funkce pro tvorbu klíče</string>
|
||||
<string name="extended_ASCII">Rozšířené ASCII</string>
|
||||
<string name="allow">Umožnit</string>
|
||||
<string name="error_load_database">Databázi se nedaří načíst.</string>
|
||||
<string name="error_load_database_KDF_memory">Klíč se nedaří načíst, zkuste snížit množství paměti, využívané funkcí pro tvorbu klíče.</string>
|
||||
<string name="error_autofill_enable_service">Službu automatického vyplňování se nedaří zapnout.</string>
|
||||
<string name="allow">Povolit</string>
|
||||
<string name="error_load_database">Databázi se nepodařilo načíst.</string>
|
||||
<string name="error_load_database_KDF_memory">Klíč se nepodařilo načíst, zkuste snížit \"využití paměti\" pro KDF.</string>
|
||||
<string name="error_autofill_enable_service">Službu automatického vyplňování se nepodařilo zapnout.</string>
|
||||
<string name="error_move_folder_in_itself">Není možné přesunout skupinu do ní samotné.</string>
|
||||
<string name="file_not_found_content">Soubor nenalezen. Zkuste jej otevřít ze správce souborů.</string>
|
||||
<string name="list_entries_show_username_title">Zobrazit uživatelská jména</string>
|
||||
<string name="list_entries_show_username_summary">V seznamech položek zobrazit uživatelská jména</string>
|
||||
<string name="list_entries_show_username_summary">V seznamech záznamů zobrazit uživatelská jména</string>
|
||||
<string name="copy_field">Kopie %1$s</string>
|
||||
<string name="menu_form_filling_settings">Vyplňování formulářů</string>
|
||||
<string name="menu_copy">Zkopírovat</string>
|
||||
<string name="menu_move">Přesunout</string>
|
||||
<string name="menu_paste">Vložit</string>
|
||||
<string name="menu_cancel">Storno</string>
|
||||
<string name="menu_cancel">Zrušit</string>
|
||||
<string name="menu_file_selection_read_only">Chráněno před zápisem</string>
|
||||
<string name="menu_open_file_read_and_write">Čtení a zápis</string>
|
||||
<string name="read_only">Chráněno před zápisem</string>
|
||||
<string name="encryption_explanation">Algoritmus šifrování databáze užitý pro všechna data.</string>
|
||||
<string name="kdf_explanation">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í.</string>
|
||||
<string name="encryption_explanation">Algoritmus šifrování databáze použit pro všechna data.</string>
|
||||
<string name="kdf_explanation">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í.</string>
|
||||
<string name="memory_usage">Využití paměti</string>
|
||||
<string name="memory_usage_explanation">Množství paměti (v bajtech) použitých funkcí pro odvození klíče.</string>
|
||||
<string name="parallelism">Souběžné zpracovávání</string>
|
||||
<string name="parallelism_explanation">Stupeň souběžného zpracovávání (počet vláken) použitý funkcí pro vytvoření klíče.</string>
|
||||
<string name="parallelism_explanation">Stupeň souběžného zpracovávání (počet vláken) použitý funkcí pro odvození klíče.</string>
|
||||
<string name="sort_menu">Seřadit</string>
|
||||
<string name="sort_ascending">Nejnižší první ↓</string>
|
||||
<string name="sort_groups_before">Skupiny první</string>
|
||||
<string name="sort_recycle_bin_bottom">Koš jako poslední</string>
|
||||
<string name="sort_title">Nadpis</string>
|
||||
<string name="sort_username">Uživatelské jméno</string>
|
||||
<string name="sort_creation_time">Vytvoření</string>
|
||||
<string name="sort_last_modify_time">Změna</string>
|
||||
<string name="sort_creation_time">Založeno</string>
|
||||
<string name="sort_last_modify_time">Změněno</string>
|
||||
<string name="sort_last_access_time">Přístup</string>
|
||||
<string name="warning">Varování</string>
|
||||
<string name="warning_password_encoding">Nepoužívejte v hesle pro databázový soubor znaky mimo znakovou sadu Latin-1 (nepoužívejte znaky s diakritikou).</string>
|
||||
<string name="warning_empty_password">Pokračovat bez ochrany odemknutím heslem\?</string>
|
||||
<string name="warning_password_encoding">Nepoužívejte v hesle pro databázový soubor znaky mimo znaky kódování textu (nerozpoznané znaky budou konvertovány na stejné písmeno).</string>
|
||||
<string name="warning_empty_password">Pokračovat bez ochrany odemknutí heslem\?</string>
|
||||
<string name="warning_no_encryption_key">Pokračovat bez šifrovacího klíče\?</string>
|
||||
<string name="encrypted_value_stored">Šifrované heslo uloženo</string>
|
||||
<string name="no_credentials_stored">Tato databáze zatím nemá uložené heslo.</string>
|
||||
@@ -198,7 +198,7 @@
|
||||
<string name="general">Obecné</string>
|
||||
<string name="autofill">Automatické vyplnění</string>
|
||||
<string name="autofill_service_name">KeePassDX automatické vyplňování formulářů</string>
|
||||
<string name="autofill_sign_in_prompt">Přihlásit se pomocí KeePassDX</string>
|
||||
<string name="autofill_sign_in_prompt">Přihlásit se s KeePassDX</string>
|
||||
<string name="set_autofill_service_title">Nastavit výchozí službu automatického vyplňování</string>
|
||||
<string name="autofill_explanation_summary">Povolit rychlé automatické vyplňování formulářů v ostatních aplikacích</string>
|
||||
<string name="password_size_title">Délka generovaného hesla</string>
|
||||
@@ -206,28 +206,28 @@
|
||||
<string name="list_password_generator_options_title">Znaky hesla</string>
|
||||
<string name="list_password_generator_options_summary">Nastavit povolené znaky pro generátor hesel</string>
|
||||
<string name="clipboard">Schránka</string>
|
||||
<string name="clipboard_notifications_title">Oznamování schránky</string>
|
||||
<string name="clipboard_notifications_summary">Ukázat oznamení schránky o kopírování pole při prohlížení záznamu</string>
|
||||
<string name="clipboard_notifications_title">Oznámení schránky</string>
|
||||
<string name="clipboard_notifications_summary">Ukázat oznámení schránky o kopírování pole při prohlížení záznamu</string>
|
||||
<string name="clipboard_warning">Vymazat historii schránky manuálně, pokud automatické vymazání schránky selže.</string>
|
||||
<string name="lock">Zamknout</string>
|
||||
<string name="lock_database_screen_off_title">Zámek obrazovky</string>
|
||||
<string name="lock_database_screen_off_summary">Při zhasnutí obrazovky uzamknout databázi</string>
|
||||
<string name="advanced_unlock">Pokročilé odemčení</string>
|
||||
<string name="biometric_unlock_enable_title">Biometrické odemčení</string>
|
||||
<string name="advanced_unlock">Rozšířené odemknutí</string>
|
||||
<string name="biometric_unlock_enable_title">Biometrické odemknutí</string>
|
||||
<string name="biometric_unlock_enable_summary">Nechá otevřít databázi snímáním biometrického údaje</string>
|
||||
<string name="biometric_delete_all_key_title">Smazat šifrovací klíče</string>
|
||||
<string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí</string>
|
||||
<string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí</string>
|
||||
<string name="unavailable_feature_text">Tuto funkci se nedaří spustit.</string>
|
||||
<string name="unavailable_feature_version">V zařízení je instalován Android %1$s, ale potřebná je verze %2$s a novější.</string>
|
||||
<string name="unavailable_feature_hardware">Hardware nebyl rozpoznán.</string>
|
||||
<string name="unavailable_feature_hardware">Odpovídající hardware nebyl rozpoznán.</string>
|
||||
<string name="file_name">Název souboru</string>
|
||||
<string name="path">Cesta</string>
|
||||
<string name="assign_master_key">Přiřadit hlavní klíč</string>
|
||||
<string name="create_keepass_file">Vytvořit novou databázi</string>
|
||||
<string name="create_keepass_file">Založit novou databázi</string>
|
||||
<string name="recycle_bin_title">Využití koše</string>
|
||||
<string name="recycle_bin_summary">Před smazáním přesune skupiny a položky do skupiny „Koš“</string>
|
||||
<string name="monospace_font_fields_enable_title">Písmo položek</string>
|
||||
<string name="monospace_font_fields_enable_summary">Čitelnost znaků v položkách můžete přizpůsobit změnou písma</string>
|
||||
<string name="monospace_font_fields_enable_title">Písmo kolonek</string>
|
||||
<string name="monospace_font_fields_enable_summary">Čitelnost znaků v kolonkách můžete přizpůsobit změnou písma</string>
|
||||
<string name="allow_copy_password_title">Důvěřovat schránce</string>
|
||||
<string name="allow_copy_password_summary">Povolit kopírování hesla záznamu a chráněných položek do schránky</string>
|
||||
<string name="allow_copy_password_warning">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.</string>
|
||||
@@ -253,47 +253,47 @@
|
||||
<string name="education_create_database_summary">Založte svůj první soubor pro správu hesel.</string>
|
||||
<string name="education_select_database_title">Otevřít existující databázi</string>
|
||||
<string name="education_select_database_summary">Otevřete svou dříve používanou databázi ze správce souborů a pokračujte v jejím používání.</string>
|
||||
<string name="education_new_node_title">Přidejte položky do databáze</string>
|
||||
<string name="education_new_node_summary">Položky pomáhají se správou vašich digitálních identit.
|
||||
\n
|
||||
<string name="education_new_node_title">Přidejte záznamy do databáze</string>
|
||||
<string name="education_new_node_summary">Záznamy pomáhají se správou Vašich digitálních identit.
|
||||
\n
|
||||
\nSkupiny (ekvivalent složek) organizují záznamy v databázi.</string>
|
||||
<string name="education_search_title">Hledejte v položkách</string>
|
||||
<string name="education_search_summary">Zadejte název, uživatelské jméno nebo jiné položky k nalezení svých hesel.</string>
|
||||
<string name="education_entry_edit_title">Upravit položku</string>
|
||||
<string name="education_entry_edit_summary">Přidejte ke své položce vlastní kolonky. Společná data mohou být sdílena mezi více různými kolonkami.</string>
|
||||
<string name="education_search_title">Hledat v záznamech</string>
|
||||
<string name="education_search_summary">Zadejte název, uživatelské jméno nebo jiné kolonky k nalezení svých hesel.</string>
|
||||
<string name="education_entry_edit_title">Upravit záznam</string>
|
||||
<string name="education_entry_edit_summary">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.</string>
|
||||
<string name="education_generate_password_title">Vytvořit silné heslo</string>
|
||||
<string name="education_generate_password_summary">Vygenerujte silné heslo pro svou položku, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.</string>
|
||||
<string name="education_generate_password_summary">Generujte silné heslo pro svůj záznam, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.</string>
|
||||
<string name="education_entry_new_field_title">Přidat vlastní kolonky</string>
|
||||
<string name="education_entry_new_field_summary">Registrovat další kolonku, zadat hodnotu a volitelně ji ochránit.</string>
|
||||
<string name="education_unlock_title">Odemknout databázi</string>
|
||||
<string name="education_read_only_title">Ochraňte svou databázi před zápisem</string>
|
||||
<string name="education_read_only_summary">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.
|
||||
\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.</string>
|
||||
<string name="education_field_copy_title">Zkopírujte kolonku</string>
|
||||
<string name="education_field_copy_summary">Zkopírované kolonky lze vkládat kam chcete
|
||||
\n
|
||||
<string name="education_field_copy_title">Zkopírovat kolonku</string>
|
||||
<string name="education_field_copy_summary">Zkopírované kolonky lze vkládat podle libosti.
|
||||
\n
|
||||
\nK vyplňování formulářů použijte svou oblíbenou metodu.</string>
|
||||
<string name="education_lock_title">Uzamkni databáze</string>
|
||||
<string name="education_lock_summary">Rychlé uzamkni databázi. Je možné nastavit, aby se databáze zamkla po určitém čase a také po zhasnutí obrazovky.</string>
|
||||
<string name="education_lock_title">Uzamknout databázi</string>
|
||||
<string name="education_lock_summary">Rychle uzamknout databázi. Je možné nastavit, aby se databáze zamkla po určitém čase a také po zhasnutí obrazovky.</string>
|
||||
<string name="education_sort_title">Řazení položek</string>
|
||||
<string name="education_sort_summary">Vyberte řazení položek a skupin.</string>
|
||||
<string name="education_donation_title">Zapojit se</string>
|
||||
<string name="education_donation_summary">Zapojte se a pomozte zvýšit stabilitu, bezpečnost a přidávání dalších funkcí.</string>
|
||||
<string name="html_text_ad_free">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.</string>
|
||||
<string name="html_text_buy_pro">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></string>
|
||||
<string name="education_donation_summary">Zapojte se a pomozte zvýšit stabilitu, bezpečnost a doplnění dalších funkcí.</string>
|
||||
<string name="html_text_ad_free">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.</string>
|
||||
<string name="html_text_buy_pro">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></string>
|
||||
<string name="html_text_feature_generosity">Tento <strong>vizuální styl</strong> je k dispozici díky vaší štědrosti.</string>
|
||||
<string name="html_text_donation">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></string>
|
||||
<string name="html_text_dev_feature">Tato funkce je <strong>ve vývoji</strong> a potřebuje váš <strong>příspěvek</strong>, aby byla brzy k dispozici.</string>
|
||||
<string name="html_text_donation">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></string>
|
||||
<string name="html_text_dev_feature">Tato funkce je <strong>ve vývoji</strong> a potřebuje Váš <strong>příspěvek</strong>, aby byla brzy k dispozici.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Zakoupením <strong>pro</strong> varianty,</string>
|
||||
<string name="html_text_dev_feature_contibute"><strong>Zapojením se</strong>,</string>
|
||||
<string name="html_text_dev_feature_encourage">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.</string>
|
||||
<string name="html_text_dev_feature_thanks">Mnohé díky za vaše přispění.</string>
|
||||
<string name="html_text_dev_feature_encourage">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.</string>
|
||||
<string name="html_text_dev_feature_thanks">Mockrát děkujeme za Váš příspěvek.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Tvrdě pracujeme na brzkém vydání této funkce.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string>
|
||||
<string name="download">Stáhnout</string>
|
||||
<string name="contribute">Zapojit se</string>
|
||||
<string name="contribute">Přispět</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="style_choose_title">Vzhled aplikace</string>
|
||||
@@ -304,16 +304,16 @@
|
||||
<string name="keyboard_name">Magikeyboard</string>
|
||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||
<string name="keyboard_setting_label">Magikeyboard nastavení</string>
|
||||
<string name="keyboard_entry_category">Položka</string>
|
||||
<string name="keyboard_entry_category">Záznam</string>
|
||||
<string name="keyboard_entry_timeout_title">Časový limit</string>
|
||||
<string name="keyboard_entry_timeout_summary">Doba uchování položky v Magikeyboardu</string>
|
||||
<string name="keyboard_notification_entry_title">Informace o oznámení</string>
|
||||
<string name="keyboard_notification_entry_summary">Zobrazit oznámení, když je položka dostupná</string>
|
||||
<string name="keyboard_notification_entry_content_title_text">Položka</string>
|
||||
<string name="keyboard_notification_entry_content_title_text">Záznam</string>
|
||||
<string name="keyboard_notification_entry_content_title">%1$s dostupné v Magikeyboardu</string>
|
||||
<string name="keyboard_notification_entry_content_text">%1$s</string>
|
||||
<string name="keyboard_notification_entry_clear_close_title">Vymazat při zavření</string>
|
||||
<string name="keyboard_notification_entry_clear_close_summary">Zavři databázi při zavření oznámení</string>
|
||||
<string name="keyboard_notification_entry_clear_close_summary">Zavřít databázi při zavření oznámení</string>
|
||||
<string name="keyboard_appearance_category">Vzhled</string>
|
||||
<string name="keyboard_theme_title">Vzhled klávesnice</string>
|
||||
<string name="keyboard_keys_category">Klávesy</string>
|
||||
@@ -326,37 +326,37 @@
|
||||
<string name="clear_clipboard_notification_title">Vymazat při ukončení</string>
|
||||
<string name="clear_clipboard_notification_summary">Uzamknout databázi, jakmile trvání schránky vyprší nebo po uzavření oznámení</string>
|
||||
<string name="recycle_bin">Koš</string>
|
||||
<string name="keyboard_selection_entry_title">Výběr položky</string>
|
||||
<string name="keyboard_selection_entry_summary">Při prohlížení záznamu ukázat na Magikeyboard pole položek</string>
|
||||
<string name="keyboard_selection_entry_title">Výběr záznamu</string>
|
||||
<string name="keyboard_selection_entry_summary">Při prohlížení záznamu ukázat na Magikeyboard kolonky</string>
|
||||
<string name="delete_entered_password_title">Smazat heslo</string>
|
||||
<string name="delete_entered_password_summary">Smaže heslo zadané po pokusu o připojení k databázi</string>
|
||||
<string name="content_description_open_file">Otevřít soubor</string>
|
||||
<string name="content_description_node_children">Potomci uzlu</string>
|
||||
<string name="content_description_add_node">Přidej uzel</string>
|
||||
<string name="content_description_add_entry">Přidej záznam</string>
|
||||
<string name="content_description_node_children">Podřazené prvky uzlu</string>
|
||||
<string name="content_description_add_node">Přidat uzel</string>
|
||||
<string name="content_description_add_entry">Přidat záznam</string>
|
||||
<string name="content_description_add_group">Přidat skupinu</string>
|
||||
<string name="content_description_file_information">Informace o souboru</string>
|
||||
<string name="content_description_password_checkbox">Checkbox hesla</string>
|
||||
<string name="content_description_keyfile_checkbox">Checkbox souboru s klíčem</string>
|
||||
<string name="content_description_repeat_toggle_password_visibility">Přepni ukázání hesla</string>
|
||||
<string name="content_description_repeat_toggle_password_visibility">Opakovat přepnutí viditelnosti hesla</string>
|
||||
<string name="content_description_entry_icon">Ikona záznamu</string>
|
||||
<string name="entry_password_generator">Generátor hesel</string>
|
||||
<string name="content_description_password_length">Délka hesla</string>
|
||||
<string name="entry_add_field">Přidej pole</string>
|
||||
<string name="content_description_remove_field">Odeber pole</string>
|
||||
<string name="entry_add_field">Přidat pole</string>
|
||||
<string name="content_description_remove_field">Odebrat pole</string>
|
||||
<string name="entry_UUID">UUID</string>
|
||||
<string name="error_move_entry_here">Sem záznam přesunout nelze.</string>
|
||||
<string name="error_copy_entry_here">Sem záznam zkopírovat nelze.</string>
|
||||
<string name="list_groups_show_number_entries_title">Ukaž počet záznamů</string>
|
||||
<string name="list_groups_show_number_entries_summary">Ukaž počet záznamů ve skupině</string>
|
||||
<string name="list_groups_show_number_entries_title">Ukázat počet záznamů</string>
|
||||
<string name="list_groups_show_number_entries_summary">Ukázat počet záznamů ve skupině</string>
|
||||
<string name="content_description_background">Pozadí</string>
|
||||
<string name="content_description_update_from_list">Aktualizovat</string>
|
||||
<string name="content_description_keyboard_close_fields">Zavři kolonky</string>
|
||||
<string name="error_create_database_file">Nelze vytvořit databázi s tímto heslem a klíčem ze souboru.</string>
|
||||
<string name="menu_advanced_unlock_settings">Pokročilé odemčení</string>
|
||||
<string name="content_description_keyboard_close_fields">Zavřít pole</string>
|
||||
<string name="error_create_database_file">Nelze vytvořit databázi s tímto heslem a souborem klíče.</string>
|
||||
<string name="menu_advanced_unlock_settings">Rozšířené odemknutí</string>
|
||||
<string name="biometric">Biometrika</string>
|
||||
<string name="biometric_auto_open_prompt_title">Automaticky otevřít pobídku</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automaticky žádat pokročilé odemknutí, je-li databáze nastavena k jejímu použití</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automaticky žádat rozšířené odemknutí, je-li databáze nastavena k jejímu použití</string>
|
||||
<string name="enable">Zapnout</string>
|
||||
<string name="disable">Vypnout</string>
|
||||
<string name="master_key">Hlavní klíč</string>
|
||||
@@ -372,7 +372,7 @@
|
||||
<string name="entry_otp">OTP</string>
|
||||
<string name="error_invalid_OTP">Neplatná OTP tajnost.</string>
|
||||
<string name="error_disallow_no_credentials">Nejméně jeden přihlašovací údaj musí být zadán.</string>
|
||||
<string name="error_copy_group_here">Sem skupinu kopírovat nemůžete.</string>
|
||||
<string name="error_copy_group_here">Sem skupinu kopírovat nelze.</string>
|
||||
<string name="error_otp_secret_key">Tajný klíč musí mít formát Base32.</string>
|
||||
<string name="error_otp_counter">Čítač musít být mezi %1$d a %2$d.</string>
|
||||
<string name="error_otp_period">Interval musít být mezi %1$d a %2$d vteřinami.</string>
|
||||
@@ -384,8 +384,8 @@
|
||||
<string name="contains_duplicate_uuid">Databáze obsahuje duplikátní UUID.</string>
|
||||
<string name="contains_duplicate_uuid_procedure">Opravit chybu založením nového UUID pro duplikáty a pokračovat\?</string>
|
||||
<string name="database_opened">Databáze otevřena</string>
|
||||
<string name="clipboard_explanation_summary">Kopírujte pole záznamů pomocí schránky Vašeho zařízení</string>
|
||||
<string name="advanced_unlock_explanation_summary">K snadnějšímu otevření databáze použijte pokročilé odemknutí</string>
|
||||
<string name="clipboard_explanation_summary">Kopírovat kolonky záznamů pomocí schránky svého zařízení</string>
|
||||
<string name="advanced_unlock_explanation_summary">K snadnějšímu otevření databáze použijte rozšířené odemknutí</string>
|
||||
<string name="database_data_compression_title">Komprese dat</string>
|
||||
<string name="database_data_compression_summary">Komprese dat snižuje velikost databáze</string>
|
||||
<string name="max_history_items_title">Maximální počet</string>
|
||||
@@ -413,20 +413,20 @@
|
||||
<string name="recycle_bin_group_title">Skupina Koš</string>
|
||||
<string name="enable_auto_save_database_title">Uložit databázi automaticky</string>
|
||||
<string name="enable_auto_save_database_summary">Uložit databázi po každé důležité akci (v režimu \"Zápis\")</string>
|
||||
<string name="entry_attachments">Připojené soubory</string>
|
||||
<string name="entry_attachments">Přílohy</string>
|
||||
<string name="menu_restore_entry_history">Obnovit historii</string>
|
||||
<string name="menu_delete_entry_history">Smazat historii</string>
|
||||
<string name="keyboard_auto_go_action_title">Akce auto-klávesy</string>
|
||||
<string name="keyboard_auto_go_action_summary">Akce klávesy \"Jít\" po stisknutí klávesy \"Položka\"</string>
|
||||
<string name="keyboard_auto_go_action_summary">Akce klávesy \"Jít\" po stisknutí klávesy \"Kolonka\"</string>
|
||||
<string name="download_attachment">Stáhnout %1$s</string>
|
||||
<string name="download_initialization">Zahajuji…</string>
|
||||
<string name="download_progression">Probíhá: %1$d%%</string>
|
||||
<string name="download_finalization">Dokončuji…</string>
|
||||
<string name="download_complete">Kompletní!</string>
|
||||
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</string>
|
||||
<string name="hide_expired_entries_summary">Propadlé záznamy nejsou ukázány</string>
|
||||
<string name="hide_expired_entries_summary">Propadlé záznamy nebudou ukázány</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="contribution">Příspěvky</string>
|
||||
<string name="contribution">Příspění</string>
|
||||
<string name="feedback">Feedback</string>
|
||||
<string name="auto_focus_search_title">Snadné hledání</string>
|
||||
<string name="auto_focus_search_summary">Při otevření databáze žádat hledání</string>
|
||||
@@ -436,23 +436,23 @@
|
||||
<string name="remember_keyfile_locations_summary">Uchová informaci o tom, kde jsou uloženy soubory s klíči</string>
|
||||
<string name="show_recent_files_title">Ukázat nedávné soubory</string>
|
||||
<string name="show_recent_files_summary">Ukázat umístění nedávných databází</string>
|
||||
<string name="hide_broken_locations_title">Skrýt špatné odkazy na databáze</string>
|
||||
<string name="hide_broken_locations_summary">Skrýt nesprávné odkazy v seznamu nedávných databází</string>
|
||||
<string name="hide_broken_locations_title">Skrýt chybné odkazy na databáze</string>
|
||||
<string name="hide_broken_locations_summary">Skrýt chybné odkazy v seznamu nedávných databází</string>
|
||||
<string name="warning_database_read_only">Udělit právo zápisu pro uložení změn v databázi</string>
|
||||
<string name="html_about_licence">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.</string>
|
||||
<string name="html_about_contribution">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>.</string>
|
||||
<string name="error_create_database">Nedaří se vytvořit soubor s databází.</string>
|
||||
<string name="error_create_database">Nepodařilo se vytvořit soubor databáze.</string>
|
||||
<string name="entry_add_attachment">Přidat přílohu</string>
|
||||
<string name="discard">Zahodit</string>
|
||||
<string name="discard_changes">Zahodit změny\?</string>
|
||||
<string name="validate">Ověřit</string>
|
||||
<string name="discard">Zavrhnout</string>
|
||||
<string name="discard_changes">Zavrhnout změny\?</string>
|
||||
<string name="validate">Zkontrolovat</string>
|
||||
<string name="education_setup_OTP_summary">Nastavit správu One-Time hesla (HOTP / TOTP) pro založení tokenu požadovaného pro dvoufázové ověření (2FA).</string>
|
||||
<string name="education_setup_OTP_title">Nastavit OTP</string>
|
||||
<string name="autofill_auto_search_summary">Automaticky navrhnout výsledky hledání z webové domény nebo ID aplikace</string>
|
||||
<string name="autofill_auto_search_title">Samočinné hledání</string>
|
||||
<string name="lock_database_show_button_summary">Ukáže tlačítko zámku v uživatelském rozhraní</string>
|
||||
<string name="lock_database_show_button_title">Ukázat tlačítko zámku</string>
|
||||
<string name="lock_database_show_button_summary">Zobrazí tlačítko zámku v uživatelském rozhraní</string>
|
||||
<string name="lock_database_show_button_title">Zobrazit tlačítko zámku</string>
|
||||
<string name="autofill_preference_title">Nastavení samovyplnění</string>
|
||||
<string name="warning_database_link_revoked">Přístup k souboru zrušenému správcem souborů</string>
|
||||
<string name="error_label_exists">Tento štítek již existuje.</string>
|
||||
@@ -469,14 +469,14 @@
|
||||
<string name="subdomain_search_title">Hledat v subdoméně</string>
|
||||
<string name="error_string_type">Tento text se s požadovanou položkou neshoduje.</string>
|
||||
<string name="content_description_add_item">Přidat položku</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Automaticky přepnout na předchozí klávesnici po provedení Akce auto-klávesy</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Automaticky přepnout na předchozí klávesnici po provedení \"Akce auto-klávesy\"</string>
|
||||
<string name="keyboard_previous_fill_in_title">Akce auto-klávesy</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Automaticky přepnout zpět na předchozí klávesnici na obrazovce ověřovacích údajů databáze</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Obrazovka ověřovacích údajů databáze</string>
|
||||
<string name="keyboard_change">Přepnout klávesnici</string>
|
||||
<string name="upload_attachment">Nahrát %1$s</string>
|
||||
<string name="content_description_credentials_information">Info o údajích</string>
|
||||
<string name="warning_file_too_big">Databáze KeePassu předpokládá uchovávat jen malé pomocné sobory (např. PGP soubory).
|
||||
<string name="content_description_credentials_information">Informace o údajích</string>
|
||||
<string name="warning_file_too_big">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.</string>
|
||||
<string name="warning_replace_file">Nahráním tohoto souboru nahradíte existující soubor.</string>
|
||||
@@ -497,7 +497,7 @@
|
||||
<string name="autofill_ask_to_save_data_title">Zeptat se před uložením</string>
|
||||
<string name="autofill_save_search_info_summary">Pokuste se uložit údaje hledání, když manuálně vybíráte položku</string>
|
||||
<string name="autofill_save_search_info_title">Uložit info hledání</string>
|
||||
<string name="autofill_close_database_summary">Zavřít databázi po samodoplnění polí</string>
|
||||
<string name="autofill_close_database_summary">Zavřít databázi po samovyplnění polí</string>
|
||||
<string name="autofill_close_database_title">Zavřít databázi</string>
|
||||
<string name="keyboard_previous_lock_summary">Po uzamknutí databáze automaticky přepnout zpět na předchozí klávesnici</string>
|
||||
<string name="keyboard_previous_lock_title">Uzamknout databázi</string>
|
||||
@@ -506,31 +506,44 @@
|
||||
<string name="notification">Oznámení</string>
|
||||
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string>
|
||||
<string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string>
|
||||
<string name="warning_empty_recycle_bin">Trvale odstranit všechny položky z koše\?</string>
|
||||
<string name="warning_empty_recycle_bin">Trvale odstranit všechny uzly z koše\?</string>
|
||||
<string name="registration_mode">Režim registrace</string>
|
||||
<string name="save_mode">Režim ukládání</string>
|
||||
<string name="search_mode">Režim vyhledávání</string>
|
||||
<string name="error_field_name_already_exists">Jméno položky již existuje.</string>
|
||||
<string name="error_field_name_already_exists">Jméno kolonky již existuje.</string>
|
||||
<string name="error_registration_read_only">Uložení nové položky v režimu databáze pouze pro čtení není povoleno</string>
|
||||
<string name="enter">Enter</string>
|
||||
<string name="backspace">Backspace</string>
|
||||
<string name="select_entry">Vybrat záznam</string>
|
||||
<string name="back_to_previous_keyboard">Zpět na předchozí klávesnici</string>
|
||||
<string name="custom_fields">Vlastní položky</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí\?</string>
|
||||
<string name="custom_fields">Vlastní kolonky</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí\?</string>
|
||||
<string name="device_credential_unlock_enable_summary">Dovolí pro otevření databáze použít heslo Vašeho zařízení</string>
|
||||
<string name="device_credential_unlock_enable_title">Odemknutí heslem zařízení</string>
|
||||
<string name="device_credential">Heslo zařízení</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Zadejte heslo a klikněte na tlačítko \"Pokročilé odemknutí\".</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Nelze inicializovat pobídku pro pokročilé odemknutí.</string>
|
||||
<string name="advanced_unlock_scanning_error">Chyba při pokročilém odemknutí: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Otisk pro pokročilé odemknutí nebyl rozpoznán</string>
|
||||
<string name="advanced_unlock_invalid_key">Nelze načíst klíč pokročilého odemknutí. Prosím, smažte jej a opakujte proces rozpoznání uzamknutí.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Načíst důvěrný údaj pomocí dat pokročilého odemknutí</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Otevřít databázi pomocí rozpoznání pokročilého odemknutí</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Varování: Pokud použijete rozpoznání pokročilého odemknutí, musíte si i nadále pamatovat hlavní heslo.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Rozpoznání pokročilého odemknutí</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Pro uložení důvěrných údajů otevřete pobídku pokročilého odemknutí</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Pro odemknutí databáze otevřete pobídku pokročilého odemknutí</string>
|
||||
<string name="menu_keystore_remove_key">Smazat klíč pokročilého odemknutí</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Zadejte heslo a klikněte na tlačítko \"Rozšířené odemknutí\".</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Nelze inicializovat pobídku pro rozšířené odemknutí.</string>
|
||||
<string name="advanced_unlock_scanning_error">Chyba při rozšířeném odemknutí: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Otisk pro rozšířené odemknutí nebyl rozpoznán</string>
|
||||
<string name="advanced_unlock_invalid_key">Nelze načíst klíč rozšířeného odemknutí. Prosím, smažte jej a opakujte proces rozpoznání odemknutí.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Načíst důvěrný údaj pomocí dat rozšířeného odemknutí</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Otevřít databázi pomocí rozpoznání rozšířeného odemknutí</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Varování: Pokud použijete rozpoznání rozšířeného odemknutí, musíte si i nadále pamatovat hlavní heslo.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Rozpoznání rozšířeného odemknutí</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Pro uložení důvěrných údajů otevřete pobídku rozšířeného odemknutí</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Pro odemknutí databáze otevřete pobídku rozšířeného odemknutí</string>
|
||||
<string name="menu_keystore_remove_key">Smazat klíč rozšířeného odemknutí</string>
|
||||
<string name="education_advanced_unlock_title">Rozšířené odemknutí databáze</string>
|
||||
<string name="advanced_unlock_timeout">Časový limit rozšířeného odemknutí</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Trvání použití rozšířeného odemknutí než bude obsah téhož smazán</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Za účelem rozšířeného odemknutí neukládat žádný šifrovaný obsah</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Přechodné rozšířené odemknutí</string>
|
||||
<string name="advanced_unlock_tap_delete">Pro odstranění klíčů rozšířeného odemknutí klepnout</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="education_advanced_unlock_summary">Abyste rychle odemknuli databázi, propojte své heslo s naskenovanou biometrikou nebo údaji zámku zařízení.</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Vypršení pokročilého odemknutí</string>
|
||||
<string name="content">Obsah</string>
|
||||
<string name="error_rebuild_list">Seznam nelze řádně sestavit.</string>
|
||||
<string name="error_database_uri_null">URI databáze nelze načíst.</string>
|
||||
</resources>
|
||||
@@ -118,8 +118,8 @@
|
||||
<string name="never">Nie</string>
|
||||
<string name="no_results">Keine Suchergebnisse</string>
|
||||
<string name="no_url_handler">Bitte einen Webbrowser installieren, um diese URL zu öffnen.</string>
|
||||
<string name="omit_backup_search_title">Papierkorb/Sicherung nicht durchsuchen</string>
|
||||
<string name="omit_backup_search_summary">Die Gruppen „Sicherung“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt</string>
|
||||
<string name="omit_backup_search_title">Recycle bin und Backup nicht durchsuchen</string>
|
||||
<string name="omit_backup_search_summary">Die Gruppen „Backup“ und „Recycle bin“ werden bei der Suche nicht berücksichtigt</string>
|
||||
<string name="auto_focus_search_title">Schnellsuche</string>
|
||||
<string name="auto_focus_search_summary">Beim Öffnen einer Datenbank eine Suche veranlassen</string>
|
||||
<string name="progress_create">Neue Datenbank anlegen …</string>
|
||||
@@ -218,7 +218,7 @@
|
||||
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen</string>
|
||||
<string name="clipboard">Zwischenablage</string>
|
||||
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
|
||||
<string name="biometric_delete_all_key_summary">Alle mit der biometrischen Erkennung verbundenen Verschlüsselungsschlüssel löschen.</string>
|
||||
<string name="biometric_delete_all_key_summary">Lösche alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen</string>
|
||||
<string name="unavailable_feature_version">Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.</string>
|
||||
<string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string>
|
||||
<string name="recycle_bin_title">Papierkorb-Nutzung</string>
|
||||
@@ -231,7 +231,7 @@
|
||||
<string name="database_description_title">Datenbankbeschreibung</string>
|
||||
<string name="database_version_title">Datenbankversion</string>
|
||||
<string name="text_appearance">Text</string>
|
||||
<string name="application_appearance">App</string>
|
||||
<string name="application_appearance">Interface</string>
|
||||
<string name="other">Andere</string>
|
||||
<string name="keyboard">Tastatur</string>
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
@@ -267,7 +267,7 @@
|
||||
<string name="education_donation_title">Mitmachen</string>
|
||||
<string name="education_donation_summary">Mithelfen, um Stabilität und Sicherheit zu verbessern und weitere Funktionen zu ermöglichen.</string>
|
||||
<string name="html_text_ad_free">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).</string>
|
||||
<string name="html_text_buy_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></string>
|
||||
<string name="html_text_buy_pro">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></string>
|
||||
<string name="html_text_feature_generosity">Dieser <strong>visuelle Stil</strong> wurde wegen Ihrer Großzügigkeit freigeschaltet.</string>
|
||||
<string name="html_text_donation">Um unsere Freiheit zu bewahren und immer aktiv zu bleiben, zählen wir auf Ihren <strong>Beitrag.</strong></string>
|
||||
<string name="html_text_dev_feature">Diese Funktion ist <strong>in Entwicklung</strong> und erfordert <strong>Ihren Beitrag</strong>, um bald verfügbar zu sein.</string>
|
||||
@@ -276,7 +276,7 @@
|
||||
<string name="html_text_dev_feature_encourage">Sie ermutigen die Entwickler:innen, <strong>neue Funktionen</strong> einzuführen und gemäß Ihren Anmerkungen <strong>Fehler zu beheben</strong>.</string>
|
||||
<string name="html_text_dev_feature_thanks">Vielen Dank für Ihre Unterstützung.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Wir bemühen uns, diese Funktion bald zu veröffentlichen.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Vergessen Sie nicht, Ihre Anwendung aktuell zu halten, indem Sie neue Versionen installieren.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Denken Sie daran, Ihre App auf dem neuesten Stand zu halten, indem Sie neue Versionen installieren.</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="contribute">Unterstützen</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
@@ -299,10 +299,10 @@
|
||||
<string name="enable_read_only_title">Schreibgeschützt</string>
|
||||
<string name="education_read_only_title">Schreibschutz der Datenbank aktivieren</string>
|
||||
<string name="enable_read_only_summary">Datenbank standardmäßig schreibgeschützt öffnen</string>
|
||||
<string name="education_read_only_summary">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.</string>
|
||||
<string name="education_read_only_summary">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.</string>
|
||||
<string name="edit_entry">Eintrag bearbeiten</string>
|
||||
<string name="error_load_database">Datenbank kann nicht geladen werden.</string>
|
||||
<string name="error_load_database_KDF_memory">Laden des Schlüssels fehlgeschlagen. Bitte versuchen, die „Speicherplatznutzung“ von KDF zu verringern.</string>
|
||||
@@ -336,7 +336,7 @@
|
||||
<string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string>
|
||||
<string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string>
|
||||
<string name="hide_broken_locations_summary">Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden</string>
|
||||
<string name="do_not_kill_app">Nicht die Anwendung beenden …</string>
|
||||
<string name="do_not_kill_app">App nicht beenden …</string>
|
||||
<string name="lock_database_back_root_summary">Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird</string>
|
||||
<string name="clear_clipboard_notification_title">Beim Schließen löschen</string>
|
||||
<string name="recycle_bin">Papierkorb</string>
|
||||
@@ -373,8 +373,8 @@
|
||||
<string name="biometric">Biometrisch</string>
|
||||
<string name="enable">Aktivieren</string>
|
||||
<string name="disable">Deaktivieren</string>
|
||||
<string name="biometric_auto_open_prompt_title">Biometrische Abfrage automatisch öffnen</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatisch nach Biometrie fragen, wenn die Datenbank dafür eingerichtet ist</string>
|
||||
<string name="biometric_auto_open_prompt_title">Abfrage automatisch öffnen</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatisch nach der erweiterten Entsperrung fragen, wenn die Datenbank dafür eingerichtet ist</string>
|
||||
<string name="master_key">Hauptschlüssel</string>
|
||||
<string name="security">Sicherheit</string>
|
||||
<string name="entry_history">Verlauf</string>
|
||||
@@ -500,7 +500,7 @@
|
||||
<string name="upload_attachment">%1$s hochladen</string>
|
||||
<string name="education_add_attachment_title">Anhang hinzufügen</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind</string>
|
||||
<string name="warning_sure_add_file">Die Datei trotzdem hinzufügen\?</string>
|
||||
<string name="warning_sure_add_file">Soll die Datei trotzdem hinzugefügt werden\?</string>
|
||||
<string name="show_uuid_summary">Zeigt die mit einem Eintrag verknüpfte UUID an</string>
|
||||
<string name="show_uuid_title">UUID anzeigen</string>
|
||||
<string name="autofill_read_only_save">Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.</string>
|
||||
@@ -527,7 +527,7 @@
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string>
|
||||
<string name="menu_keystore_remove_key">Löschen des Schlüssels zum erweiterten Entsperren</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Fortschrittliche Entsperrerkennung</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Erweiterte Entsperrerkennung</string>
|
||||
<string name="education_advanced_unlock_summary">Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.</string>
|
||||
<string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string>
|
||||
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string>
|
||||
@@ -535,16 +535,16 @@
|
||||
<string name="temp_advanced_unlock_timeout_title">Verfall der erweiterten Entsperrung</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Temporäre erweiterte Entsperrung</string>
|
||||
<string name="device_credential_unlock_enable_summary">Erlaubt Ihnn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
|
||||
<string name="device_credential_unlock_enable_summary">Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
|
||||
<string name="advanced_unlock_tap_delete">Drücken um erweiterte Entsperrschlüssel zu löschen</string>
|
||||
<string name="content">Inhalt</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit fortgeschriitener Entsperrungs-Erkennung</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit erweiterter Entsperrerkennung</string>
|
||||
<string name="enter">Eingabetaste</string>
|
||||
<string name="backspace">Rücktaste</string>
|
||||
<string name="select_entry">Wähle Eintrag</string>
|
||||
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
|
||||
<string name="custom_fields">Benutzerdefinierte Felder</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Löschen aller Schlüssel in Zusammenhang mit Erkennung des erweiterterten Entsperrens\?</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?</string>
|
||||
<string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string>
|
||||
<string name="device_credential">Geräteanmeldedaten</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.</string>
|
||||
@@ -553,4 +553,8 @@
|
||||
<string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string>
|
||||
<string name="advanced_unlock_invalid_key">Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">Die Liste kann nicht ordnungsgemäß neu erstellt werden.</string>
|
||||
<string name="error_database_uri_null">Datenbank-URI kann nicht abgerufen werden.</string>
|
||||
</resources>
|
||||
@@ -541,4 +541,8 @@
|
||||
<string name="temp_advanced_unlock_timeout_title">Λήξη προηγμένου ξεκλειδώματος</string>
|
||||
<string name="advanced_unlock_tap_delete">Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος</string>
|
||||
<string name="content">Περιεχόμενα</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">Δεν είναι δυνατή η σωστή αναδημιουργία της λίστας.</string>
|
||||
<string name="error_database_uri_null">Δεν είναι δυνατή η ανάκτηση του URI βάσης δεδομένων.</string>
|
||||
</resources>
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
Spanish translation by José I. Paños. Updated by David García-Abad (23-09-2013)
|
||||
--><resources>
|
||||
<string name="feedback">Commentario</string>
|
||||
<string name="feedback">Comentarios</string>
|
||||
<string name="homepage">Página de inicio</string>
|
||||
<string name="about_description">Implementación para Android del gestor de contraseñas KeePass</string>
|
||||
<string name="about_description">Implementación en Android del gestor de contraseñas KeePass</string>
|
||||
<string name="accept">Aceptar</string>
|
||||
<string name="add_entry">Añadir entrada</string>
|
||||
<string name="add_group">Añadir grupo</string>
|
||||
@@ -31,30 +31,30 @@
|
||||
<string name="application">Aplicación</string>
|
||||
<string name="menu_app_settings">Configuración de la aplicación</string>
|
||||
<string name="brackets">Paréntesis</string>
|
||||
<string name="file_manager_install_description">Explora ficheros con OpenIntents File Manager</string>
|
||||
<string name="file_manager_install_description">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.</string>
|
||||
<string name="clipboard_cleared">Portapapeles limpiado</string>
|
||||
<string name="clipboard_timeout">Portapapeles caducado</string>
|
||||
<string name="clipboard_timeout_summary">Duración del almacenamiento en el portapapeles (si lo admite el dispositivo)</string>
|
||||
<string name="clipboard_timeout_summary">Duración del almacenamiento en el portapapeles (si lo admite su dispositivo)</string>
|
||||
<string name="select_to_copy">Seleccionar para copiar %1$s en el portapapeles</string>
|
||||
<string name="retrieving_db_key">Creando clave de la base de datos…</string>
|
||||
<string name="retrieving_db_key">Recuperando la clave de la base de datos…</string>
|
||||
<string name="database">Base de datos</string>
|
||||
<string name="decrypting_db">Descifrando el contenido de la base de datos…</string>
|
||||
<string name="default_checkbox">Utilice como base de datos por defecto</string>
|
||||
<string name="default_checkbox">Utilizar como base de datos por defecto</string>
|
||||
<string name="digits">Dígitos</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft es \u0020de <strong>código abierto</strong> y <strong>sin publicidad</strong>.
|
||||
<string name="html_about_licence">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.</string>
|
||||
<string name="select_database_file">Abrir base de datos existente</string>
|
||||
<string name="entry_accessed">Acceso</string>
|
||||
<string name="entry_accessed">Accedido</string>
|
||||
<string name="entry_cancel">Cancelar</string>
|
||||
<string name="entry_notes">Notas</string>
|
||||
<string name="entry_confpassword">Confirmar contraseña</string>
|
||||
<string name="entry_created">Creación</string>
|
||||
<string name="entry_expires">Caducidad</string>
|
||||
<string name="entry_created">Creado</string>
|
||||
<string name="entry_expires">Caduca</string>
|
||||
<string name="entry_keyfile">Archivo de clave</string>
|
||||
<string name="entry_modified">Modificación</string>
|
||||
<string name="entry_modified">Modificada</string>
|
||||
<string name="entry_password">Contraseña</string>
|
||||
<string name="save">Guardar</string>
|
||||
<string name="entry_title">Nombre</string>
|
||||
<string name="entry_title">Título</string>
|
||||
<string name="entry_url">URL</string>
|
||||
<string name="entry_user_name">Nombre de usuario</string>
|
||||
<string name="error_arc4">No se admite el cifrador de flujo Arcfour.</string>
|
||||
@@ -64,11 +64,11 @@
|
||||
<string name="error_invalid_path">Asegúrese de que la ruta sea correcta.</string>
|
||||
<string name="error_no_name">Proporcione un nombre.</string>
|
||||
<string name="error_nokeyfile">Seleccione un archivo de clave.</string>
|
||||
<string name="error_out_of_memory">No queda memoria para cargar toda la base de datos.</string>
|
||||
<string name="error_out_of_memory">No hay memoria para cargar toda la base de datos.</string>
|
||||
<string name="error_pass_gen_type">Debe seleccionar al menos un tipo de generación de contraseñas.</string>
|
||||
<string name="error_pass_match">Las contraseñas no coinciden.</string>
|
||||
<string name="error_rounds_too_large">Pasadas demasiado grande. Establecido a 2147483648.</string>
|
||||
<string name="error_wrong_length">Proporcione un número entero positivo en el campo «Longitud».</string>
|
||||
<string name="error_wrong_length">Introduzca un número entero positivo en el campo \"Longitud\".</string>
|
||||
<string name="file_browser">Explorador de archivos</string>
|
||||
<string name="generate_password">Generar contraseña</string>
|
||||
<string name="hint_conf_pass">Confirmar contraseña</string>
|
||||
@@ -78,7 +78,7 @@
|
||||
<string name="hint_length">Longitud</string>
|
||||
<string name="password">Contraseña</string>
|
||||
<string name="hint_pass">Contraseña</string>
|
||||
<string name="invalid_credentials">Contraseña o archivo de clave no válido.</string>
|
||||
<string name="invalid_credentials">No se pudieron leer las credenciales.</string>
|
||||
<string name="invalid_db_sig">No se pudo reconocer el formato de la base de datos.</string>
|
||||
<string name="length">Longitud</string>
|
||||
<string name="list_size_title">Tamaño de los elementos de la lista</string>
|
||||
@@ -103,7 +103,7 @@
|
||||
<string name="minus">Menos</string>
|
||||
<string name="never">Nunca</string>
|
||||
<string name="no_results">Sin resultado de búsqueda</string>
|
||||
<string name="no_url_handler">Instale un navegador web para abrir este URL.</string>
|
||||
<string name="no_url_handler">Instale un navegador web para abrir esta URL.</string>
|
||||
<string name="omit_backup_search_title">No buscar en las entradas de respaldo</string>
|
||||
<string name="omit_backup_search_summary">Omite los grupos «Respaldo» y «Papelera de reciclaje» de los resultados de búsqueda</string>
|
||||
<string name="progress_create">Creando nueva base de datos…</string>
|
||||
@@ -124,7 +124,9 @@
|
||||
<string name="unsupported_db_version">No se admite esta versión de la base de datos.</string>
|
||||
<string name="uppercase">Mayúsculas</string>
|
||||
<string name="version_label">Versión %1$s</string>
|
||||
<string name="education_unlock_summary">Introduzca una contraseña y/o un archivo de clave para desbloquear su base de datos.</string>
|
||||
<string name="education_unlock_summary">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.</string>
|
||||
<string-array name="timeout_options">
|
||||
<item>5 segundos</item>
|
||||
<item>10 segundos</item>
|
||||
@@ -146,28 +148,28 @@
|
||||
<string name="extended_ASCII">ASCII extendido</string>
|
||||
<string name="allow">Permitir</string>
|
||||
<string name="clipboard_error_title">Error del portapapeles</string>
|
||||
<string name="clipboard_error">Algunos dispositivos Android tienen un error en la implementación del portapapeles que provoca fallos al copiar desde las aplicaciones.</string>
|
||||
<string name="clipboard_error_clear">Falló la limpieza del portapapeles</string>
|
||||
<string name="clipboard_error">Algunos dispositivos no permiten que las aplicaciones usen el portapapeles.</string>
|
||||
<string name="clipboard_error_clear">No se pudo limpiar el portapapeles</string>
|
||||
<string name="error_string_key">Cada cadena debe tener un nombre de campo.</string>
|
||||
<string name="error_autofill_enable_service">El servicio de autocompletado no se ha podido habilitar.</string>
|
||||
<string name="field_name">Nombre del campo</string>
|
||||
<string name="field_value">Valor del campo</string>
|
||||
<string name="file_not_found_content">No se pudo encontrar el archivo. Intente volver a abrirlo en el explorador de archivos.</string>
|
||||
<string name="invalid_algorithm">El algoritmo es incorrecto.</string>
|
||||
<string name="invalid_algorithm">Algoritmo incorrecto.</string>
|
||||
<string name="keyfile_is_empty">El archivo de clave está vacío.</string>
|
||||
<string name="copy_field">Copia de %1$s</string>
|
||||
<string name="menu_form_filling_settings">Llenado de formulario</string>
|
||||
<string name="protection">Protección</string>
|
||||
<string name="read_only">Protegida contra escritura</string>
|
||||
<string name="read_only_warning">KeePassDX necesita permiso de escritura para modificar la base de datos.</string>
|
||||
<string name="encryption_explanation">Algoritmo de cifrado utilizado en todos los datos.</string>
|
||||
<string name="read_only_warning">Dependiendo del administrador de archivos, puede que KeePassDX no permita escribir en su almacenamiento.</string>
|
||||
<string name="encryption_explanation">Algoritmo de cifrado utilizado para todos los datos.</string>
|
||||
<string name="kdf_explanation">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.</string>
|
||||
<string name="memory_usage">Uso de memoria</string>
|
||||
<string name="memory_usage_explanation">Cantidad de memoria (en bytes) que usará la función de derivación de clave.</string>
|
||||
<string name="parallelism">Paralelismo</string>
|
||||
<string name="parallelism_explanation">Grado de paralelismo (p. ej. número de hilos) usados por la función de derivación de clave.</string>
|
||||
<string name="sort_menu">Ordenar</string>
|
||||
<string name="sort_ascending">Inferiores primero ↓</string>
|
||||
<string name="sort_ascending">Más bajo primero ↓</string>
|
||||
<string name="sort_groups_before">Agrupar antes</string>
|
||||
<string name="sort_recycle_bin_bottom">Papelera debajo</string>
|
||||
<string name="sort_title">Título</string>
|
||||
@@ -177,8 +179,8 @@
|
||||
<string name="sort_last_access_time">Acceso</string>
|
||||
<string name="search_results">Resultados de búsqueda</string>
|
||||
<string name="warning">Atención</string>
|
||||
<string name="entry_not_found">Datos de entrada no encontrados.</string>
|
||||
<string name="warning_password_encoding">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).</string>
|
||||
<string name="entry_not_found">No se pudieron encontrar los datos de entrada.</string>
|
||||
<string name="warning_password_encoding">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).</string>
|
||||
<string name="warning_empty_password">¿Continuar sin la protección de desbloqueo de contraseña\?</string>
|
||||
<string name="warning_no_encryption_key">¿Continuar sin clave de cifrado\?</string>
|
||||
<string name="encrypted_value_stored">Contraseña cifrada almacenada</string>
|
||||
@@ -197,25 +199,25 @@
|
||||
<string name="list_password_generator_options_summary">Establecer los caracteres permitidos del generador de contraseñas</string>
|
||||
<string name="clipboard">Portapapeles</string>
|
||||
<string name="clipboard_notifications_title">Notificaciones del portapapeles</string>
|
||||
<string name="clipboard_notifications_summary">Mostrar notificaciones del portapapeles para copiar campos al examinar una entrada</string>
|
||||
<string name="clipboard_notifications_summary">Mostrar las notificaciones del portapapeles para copiar campos al examinar una entrada</string>
|
||||
<string name="lock">Bloquear</string>
|
||||
<string name="lock_database_screen_off_title">Bloqueo de pantalla</string>
|
||||
<string name="lock_database_screen_off_summary">Bloquear la base de datos cuando la pantalla esté apagada</string>
|
||||
<string name="advanced_unlock">Desbloqueo avanzado</string>
|
||||
<string name="biometric_unlock_enable_title">Desbloqueo biométrico</string>
|
||||
<string name="biometric_unlock_enable_summary">Le permite escanear su huella dactilar u otro dato biométrico para abrir la base de datos</string>
|
||||
<string name="biometric_unlock_enable_summary">Le permite escanear sus datos biométricos para abrir la base de datos</string>
|
||||
<string name="biometric_delete_all_key_title">Eliminar claves de cifrado</string>
|
||||
<string name="biometric_delete_all_key_summary">Eliminar todas las claves de cifrado relacionadas con el reconocimiento biométrico</string>
|
||||
<string name="biometric_delete_all_key_summary">Eliminar todas las claves de cifrado relacionadas con el reconocimiento de desbloqueo avanzado</string>
|
||||
<string name="unavailable_feature_text">No se pudo iniciar esta funcionalidad.</string>
|
||||
<string name="unavailable_feature_version">La versión de Android del dispositivo es %1$s, pero es necesaria la %2$s o posterior.</string>
|
||||
<string name="unavailable_feature_hardware">No se encontró el hardware correspondiente.</string>
|
||||
<string name="unavailable_feature_version">El dispositivo funciona con Android %1$s, pero necesita %2$s o posterior.</string>
|
||||
<string name="unavailable_feature_hardware">No se pudo encontrar el hardware correspondiente.</string>
|
||||
<string name="file_name">Nombre del archivo</string>
|
||||
<string name="path">Ruta</string>
|
||||
<string name="assign_master_key">Asignar una clave maestra</string>
|
||||
<string name="create_keepass_file">Crear base de datos nueva</string>
|
||||
<string name="create_keepass_file">Crear una base de datos nueva</string>
|
||||
<string name="recycle_bin_title">Uso de la papelera de reciclaje</string>
|
||||
<string name="recycle_bin_summary">Mueve los grupos y las entradas al grupo \"Papelera de reciclaje\" antes de eliminarlos</string>
|
||||
<string name="monospace_font_fields_enable_title">Fuente de los campos</string>
|
||||
<string name="monospace_font_fields_enable_title">Tipografía del campo</string>
|
||||
<string name="monospace_font_fields_enable_summary">Cambiar la fuente de los campos para una mejor visibilidad del carácter</string>
|
||||
<string name="allow_copy_password_title">Portapapeles de confianza</string>
|
||||
<string name="allow_copy_password_summary">Permitir copiar la contraseña de entrada y los campos protegidos al portapapeles</string>
|
||||
@@ -223,11 +225,11 @@
|
||||
<string name="database_description_title">Descripción de la base de datos</string>
|
||||
<string name="database_version_title">Versión de la base de datos</string>
|
||||
<string name="text_appearance">Texto</string>
|
||||
<string name="application_appearance">Aplicación</string>
|
||||
<string name="application_appearance">Interfaz</string>
|
||||
<string name="other">Otro</string>
|
||||
<string name="keyboard">Teclado</string>
|
||||
<string name="magic_keyboard_title">Teclado mágico</string>
|
||||
<string name="magic_keyboard_explanation_summary">Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente.</string>
|
||||
<string name="magic_keyboard_explanation_summary">Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente</string>
|
||||
<string name="reset_education_screens_title">Restablecer sugerencias didácticas</string>
|
||||
<string name="reset_education_screens_summary">Mostrar de nuevo toda la información didáctica</string>
|
||||
<string name="reset_education_screens_text">Se restablecieron las sugerencias didácticas</string>
|
||||
@@ -259,28 +261,28 @@
|
||||
<string name="education_donation_title">Participar</string>
|
||||
<string name="education_donation_summary">Participe para aumentar la estabilidad, la seguridad y agregar más funciones.</string>
|
||||
<string name="html_text_ad_free">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.</string>
|
||||
<string name="html_text_buy_pro">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></string>
|
||||
<string name="html_text_feature_generosity">Esta <strong>característica visual </strong>está disponible gracias a tu generosidad.</string>
|
||||
<string name="html_text_buy_pro">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></string>
|
||||
<string name="html_text_feature_generosity">Este <strong>estilo visual </strong>está disponible gracias a tu generosidad.</string>
|
||||
<string name="html_text_donation">Para mantener nuestra libertad y estar siempre vigente, contamos con tu <strong>contribución.</strong></string>
|
||||
<string name="html_text_dev_feature">Esta función está <strong>en desarrollo</strong> y requiere de tu <strong>contribución</strong> para estar disponible dentro de poco.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Al comprar la versión <strong>pro</strong>,</string>
|
||||
<string name="html_text_dev_feature_contibute">Al <strong>contribuir</strong>,</string>
|
||||
<string name="html_text_dev_feature_encourage">usted alienta a los desarrolladores a crear <strong>nuevas funciones</strong> y a <strong>errores de configuración</strong> de acuerdo con tus comentarios.</string>
|
||||
<string name="html_text_dev_feature_encourage">anima a los desarrolladores a crear <strong>nuevas funciones</strong> y a <strong>corregir errores</strong> de acuerdo con sus comentarios.</string>
|
||||
<string name="html_text_dev_feature_thanks">Muchas gracias por tu contribución.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Estamos trabajando duro para lanzar esta característica rápidamente.</string>
|
||||
<string name="html_text_dev_feature_upgrade">No olvide mantener su aplicación actualizada.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Recuerde mantener su aplicación actualizada instalando nuevas versiones.</string>
|
||||
<string name="download">Descargar</string>
|
||||
<string name="contribute">Contribuir</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES-KDF</string>
|
||||
<string name="style_choose_title">Tema de aplicación</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="style_choose_title">Tema de la aplicación</string>
|
||||
<string name="style_choose_summary">Tema utilizado en la aplicación</string>
|
||||
<string name="icon_pack_choose_title">Seleccione un paquete de iconos</string>
|
||||
<string name="icon_pack_choose_summary">Cambiar el paquete de iconos en la aplicación</string>
|
||||
<string name="edit_entry">Editar entrada</string>
|
||||
<string name="error_load_database">No se pudo cargar la base de datos.</string>
|
||||
<string name="error_load_database_KDF_memory">No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.</string>
|
||||
<string name="error_move_folder_in_itself">No se puede mover un grupo dentro de sí mismo.</string>
|
||||
<string name="error_move_folder_in_itself">No puede mover un grupo dentro de sí mismo.</string>
|
||||
<string name="list_entries_show_username_title">Enseña nombres de usuario</string>
|
||||
<string name="list_entries_show_username_summary">Enseña nombres de usuador en las listras de entradas</string>
|
||||
<string name="menu_copy">Copiar</string>
|
||||
@@ -291,13 +293,13 @@
|
||||
<string name="menu_open_file_read_and_write">Modificable</string>
|
||||
<string name="build_label">Compilación %1$s</string>
|
||||
<string name="clipboard_warning">Si la eliminación del cortapapeles falla, elimine su historial manualmente.</string>
|
||||
<string name="allow_copy_password_warning">ATENCIÓN: todas las aplicaciones comparten el portapapeles. Si copia datos confidenciales, otros programas podrían recuperarlos.</string>
|
||||
<string name="allow_copy_password_warning">Advertencia: El portapapeles es compartido por todas las aplicaciones. Si se copian datos sensibles, otros programas pueden recuperarlos.</string>
|
||||
<string name="allow_no_password_title">No permitir claves maestras</string>
|
||||
<string name="allow_no_password_summary">Activar el botón «Abrir» si no se selecciona ninguna credencial</string>
|
||||
<string name="enable_education_screens_title">Pantallas didácticas</string>
|
||||
<string name="enable_education_screens_summary">Resaltar los elementos para conocer cómo funciona la aplicación</string>
|
||||
<string name="allow_no_password_summary">Permite pulsar el botón \"Abrir\" si no se seleccionan credenciales</string>
|
||||
<string name="enable_education_screens_title">Sugerencias educativas</string>
|
||||
<string name="enable_education_screens_summary">Destacar elementos para aprender cómo funciona la aplicación</string>
|
||||
<string name="enable_read_only_title">Protegida contra escritura</string>
|
||||
<string name="enable_read_only_summary">Abrir su base de datos protegida contra escritura de manera predeterminada</string>
|
||||
<string name="enable_read_only_summary">Abrir la base de datos de solo lectura por defecto</string>
|
||||
<string name="education_read_only_title">Proteja la base de datos contra escritura</string>
|
||||
<string name="keyboard_name">Teclado mágico</string>
|
||||
<string name="keyboard_label">Teclado mágico (KeePassDX)</string>
|
||||
@@ -315,56 +317,56 @@
|
||||
<string name="keyboard_appearance_category">Apariencia</string>
|
||||
<string name="keyboard_theme_title">Tema del teclado</string>
|
||||
<string name="keyboard_keys_category">Teclas</string>
|
||||
<string name="keyboard_key_vibrate_title">Vibrar al pulsar</string>
|
||||
<string name="keyboard_key_sound_title">Emitir sonido al pulsar</string>
|
||||
<string name="keyboard_key_vibrate_title">Vibrar al pulsar tecla</string>
|
||||
<string name="keyboard_key_sound_title">Sonar al pulsar tecla</string>
|
||||
<string name="selection_mode">Modo de selección</string>
|
||||
<string name="do_not_kill_app">No cierre la aplicación…</string>
|
||||
<string name="lock_database_back_root_summary">Bloquear la base de datos cuando se pulsa el botón Atrás en la pantalla inicial</string>
|
||||
<string name="lock_database_back_root_summary">Bloquear la base de datos cuando el usuario pulse el botón atrás en la pantalla inicial</string>
|
||||
<string name="clear_clipboard_notification_title">Vaciar al cerrar</string>
|
||||
<string name="clear_clipboard_notification_summary">Bloquear la base de datos cuando la duración del portapapeles expire o la notificación sea cerrada</string>
|
||||
<string name="clear_clipboard_notification_summary">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</string>
|
||||
<string name="recycle_bin">Papelera de reciclaje</string>
|
||||
<string name="keyboard_selection_entry_title">Selección de entrada</string>
|
||||
<string name="keyboard_selection_entry_summary">Mostrar campos de entrada en el Teclado mágico al ver una entrada</string>
|
||||
<string name="delete_entered_password_title">Eliminar contraseña</string>
|
||||
<string name="delete_entered_password_summary">Elimina la contraseña proporcionada tras un intento de conexión</string>
|
||||
<string name="delete_entered_password_summary">Elimina la contraseña introducida tras un intento de conexión a una bse de datos</string>
|
||||
<string name="content_description_open_file">Abrir archivo</string>
|
||||
<string name="content_description_node_children">Elementos secundarios del nodo</string>
|
||||
<string name="content_description_node_children">Nodos hijo</string>
|
||||
<string name="content_description_add_node">Añadir nodo</string>
|
||||
<string name="content_description_add_entry">Añadir entrada</string>
|
||||
<string name="content_description_add_group">Añadir grupo</string>
|
||||
<string name="content_description_file_information">Información de archivo</string>
|
||||
<string name="content_description_password_checkbox">Casilla de contraseña</string>
|
||||
<string name="content_description_keyfile_checkbox">Casilla de archivo de clave</string>
|
||||
<string name="content_description_file_information">Información del archivo</string>
|
||||
<string name="content_description_password_checkbox">Casilla de verificación de la contraseña</string>
|
||||
<string name="content_description_keyfile_checkbox">Casilla de verificación del archivo de clave</string>
|
||||
<string name="content_description_entry_icon">Icono de entrada</string>
|
||||
<string name="entry_password_generator">Generador de contraseñas</string>
|
||||
<string name="content_description_password_length">Longitud de contraseña</string>
|
||||
<string name="entry_add_field">Añadir campo</string>
|
||||
<string name="content_description_remove_field">Eliminar campo</string>
|
||||
<string name="content_description_remove_field">Eliminar el campo</string>
|
||||
<string name="entry_UUID">UUID</string>
|
||||
<string name="error_move_entry_here">No se puede mover una entrada aquí.</string>
|
||||
<string name="error_copy_entry_here">No se puede copiar una entrada aquí.</string>
|
||||
<string name="list_groups_show_number_entries_title">Mostrar cantidad de entradas</string>
|
||||
<string name="list_groups_show_number_entries_summary">Mostrar la cantidad de entradas en un grupo</string>
|
||||
<string name="error_move_entry_here">No puede mover una entrada aquí.</string>
|
||||
<string name="error_copy_entry_here">No puede copiar una entrada aquí.</string>
|
||||
<string name="list_groups_show_number_entries_title">Mostrar el número de entradas</string>
|
||||
<string name="list_groups_show_number_entries_summary">Mostrar el número de entradas en un grupo</string>
|
||||
<string name="content_description_background">Fondo</string>
|
||||
<string name="content_description_update_from_list">Actualizar</string>
|
||||
<string name="content_description_keyboard_close_fields">Cerrar campos</string>
|
||||
<string name="error_create_database_file">No se puede crear la base de datos con esta contraseña y este archivo de clave.</string>
|
||||
<string name="menu_advanced_unlock_settings">Desbloqueo avanzado</string>
|
||||
<string name="biometric">Biometría</string>
|
||||
<string name="biometric_auto_open_prompt_title">Abrir petición de datos biométricos automáticamente</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Abrir automáticamente la petición de datos biométricos cuando se define una clave biométrica para una base de datos</string>
|
||||
<string name="biometric_auto_open_prompt_title">Abrir petición automáticamente</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Solicitar automáticamente el desbloqueo avanzado si la base de datos está configurada para utilizarlo</string>
|
||||
<string name="enable">Activar</string>
|
||||
<string name="disable">Desactivar</string>
|
||||
<string name="education_read_only_summary">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.</string>
|
||||
<string name="lock_database_back_root_title">Presione hacia atrás en la raíz para bloquear</string>
|
||||
\n\"Modificable\" le permite agregar, eliminar o modificar todos los elementos como desee.</string>
|
||||
<string name="lock_database_back_root_title">Presione \'Atrás\' para bloquear</string>
|
||||
<string name="content_description_repeat_toggle_password_visibility">Repetir la visibilidad de la contraseña</string>
|
||||
<string name="master_key">Llave maestra</string>
|
||||
<string name="master_key">Clave maestra</string>
|
||||
<string name="security">Seguridad</string>
|
||||
<string name="entry_history">Historial</string>
|
||||
<string name="entry_setup_otp">Configuración de contraseña de un solo uso</string>
|
||||
<string name="entry_setup_otp">Establecer una contraseña de un solo uso</string>
|
||||
<string name="otp_type">Tipo de contraseña de un solo uso</string>
|
||||
<string name="otp_secret">Secreto</string>
|
||||
<string name="otp_period">Período (segundos)</string>
|
||||
@@ -377,16 +379,16 @@
|
||||
<string name="error_otp_secret_key">Clave secreta debe estar en formato Base32.</string>
|
||||
<string name="error_otp_counter">Contador debe estar entre %1$d y %2$d.</string>
|
||||
<string name="error_save_database">No se puede guardar la base de datos.</string>
|
||||
<string name="error_string_type">Este texto no encaja con la información requerida.</string>
|
||||
<string name="error_string_type">Este texto no coincide con el elemento requerido.</string>
|
||||
<string name="error_create_database">No fue posible crear el archivo de base de datos.</string>
|
||||
<string name="html_about_contribution">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>.</string>
|
||||
<string name="html_about_contribution">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>.</string>
|
||||
<string name="content_description_add_item">Añadir elemento</string>
|
||||
<string name="download_complete">Descarga completa!</string>
|
||||
<string name="download_complete">¡Completado!</string>
|
||||
<string name="download_finalization">Finalizando…</string>
|
||||
<string name="download_progression">En progreso: %1$d%%</string>
|
||||
<string name="download_initialization">Inicializando…</string>
|
||||
<string name="download_attachment">Descargar %1$s</string>
|
||||
<string name="enable_auto_save_database_summary">Guardar la base de datos tras acciones importantes (en modo \"Modificable\")</string>
|
||||
<string name="enable_auto_save_database_summary">Guardar la base de datos después de cada acción importante (en modo \"Modificable\")</string>
|
||||
<string name="enable_auto_save_database_title">Guardar base de datos automáticamente</string>
|
||||
<string name="autofill_block">Bloquear autocompletado</string>
|
||||
<string name="keyboard_change">Cambiar teclado</string>
|
||||
@@ -395,12 +397,12 @@
|
||||
<string name="compression_none">Ninguna</string>
|
||||
<string name="compression">Compresión</string>
|
||||
<string name="database_default_username_title">Nombre de usuario predeterminado</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">Requerir cambiar la contraseña maestra la próxima vez (una sola vez)</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">Requerir cambiar la contraseña maestra la próxima vez (una vez)</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_title">Forzar renovación la próxima vez</string>
|
||||
<string name="settings_database_force_changing_master_key_summary">Requerir un cambio de contraseña maestra (días)</string>
|
||||
<string name="settings_database_force_changing_master_key_summary">Requerir un cambio de la contraseña maestra (días)</string>
|
||||
<string name="settings_database_force_changing_master_key_title">Forzar renovación</string>
|
||||
<string name="max_history_size_title">Tamaño máximo</string>
|
||||
<string name="advanced_unlock_explanation_summary">Usar desbloqueo avanzado para abrir una base de datos de manera más fácil</string>
|
||||
<string name="advanced_unlock_explanation_summary">Usar el desbloqueo avanzado para abrir una base de datos más fácilmente</string>
|
||||
<string name="lock_database_show_button_summary">Muestra el botón de bloqueo en la interfaz</string>
|
||||
<string name="lock_database_show_button_title">Mostrar botón de bloqueo</string>
|
||||
<string name="autofill_preference_title">Configuración de autocompletado</string>
|
||||
@@ -409,8 +411,8 @@
|
||||
<string name="warning_database_read_only">Otorga acceso de escritura para guardar cambios en la base de datos</string>
|
||||
<string name="show_recent_files_summary">Mostrar ubicaciones de bases de datos recientes</string>
|
||||
<string name="show_recent_files_title">Mostrar archivos recientes</string>
|
||||
<string name="remember_database_locations_summary">Recordar la ubicación de las bases de datos</string>
|
||||
<string name="remember_database_locations_title">Guardar ubicaciones de bases de datos</string>
|
||||
<string name="remember_database_locations_summary">Lleva un registro de los lugares donde se almacenan las bases de datos</string>
|
||||
<string name="remember_database_locations_title">Recordar las ubicaciones de las bases de datos</string>
|
||||
<string name="contains_duplicate_uuid">La base de datos contiene UUIDs duplicados.</string>
|
||||
<string name="menu_restore_entry_history">Restaurar historial</string>
|
||||
<string name="menu_empty_recycle_bin">Vaciar papelera de reciclaje</string>
|
||||
@@ -419,12 +421,12 @@
|
||||
<string name="invalid_db_same_uuid">%1$s con la misma UUID %2$s ya existe.</string>
|
||||
<string name="error_label_exists">Esta etiqueta ya existe.</string>
|
||||
<string name="discard">Descartar</string>
|
||||
<string name="discard_changes">¿Descartar cambios\?</string>
|
||||
<string name="discard_changes">¿Descartar los cambios\?</string>
|
||||
<string name="validate">Validar</string>
|
||||
<string name="contribution">Contribución</string>
|
||||
<string name="contact">Contactar</string>
|
||||
<string name="error_otp_period">Período debe estar entre %1$d y %2$d segundos.</string>
|
||||
<string name="error_copy_group_here">No se puede copiar un grupo aquí.</string>
|
||||
<string name="contact">Contacto</string>
|
||||
<string name="error_otp_period">El período debe estar entre %1$d y %2$d segundos.</string>
|
||||
<string name="error_copy_group_here">No puede copiar un grupo aquí.</string>
|
||||
<string name="database_data_compression_summary">La compresión de datos reduce el tamaño de la base de datos</string>
|
||||
<string name="database_data_compression_title">Compresión de datos</string>
|
||||
<string name="warning_empty_keyfile">No se recomienda agregar un archivo de claves vacío.</string>
|
||||
@@ -433,19 +435,19 @@
|
||||
<string name="warning_replace_file">Al cargar este archivo reemplazará el existente.</string>
|
||||
<string name="warning_permanently_delete_nodes">¿Borrar los nodos seleccionados de forma permanente\?</string>
|
||||
<string name="warning_database_link_revoked">El acceso al archivo fue revocado por el administrador de archivos</string>
|
||||
<string name="command_execution">Ejecutando el comando …</string>
|
||||
<string name="command_execution">Ejecutando el comando…</string>
|
||||
<string name="hide_broken_locations_summary">Ocultar enlaces rotos en la lista de bases de datos recientes</string>
|
||||
<string name="hide_broken_locations_title">Ocultar enlaces de bases de datos rotos</string>
|
||||
<string name="remember_keyfile_locations_summary">Realiza un seguimiento de dónde se almacenan los archivos de claves</string>
|
||||
<string name="remember_keyfile_locations_title">Recuerdar las ubicaciones de los archivos de claves</string>
|
||||
<string name="remember_keyfile_locations_title">Recordar las ubicaciones de los archivos de clave</string>
|
||||
<string name="subdomain_search_summary">Buscar dominios web con restricciones de subdominios</string>
|
||||
<string name="subdomain_search_title">Búsqueda de subdominio</string>
|
||||
<string name="auto_focus_search_summary">Solicite una búsqueda al abrir una base de datos</string>
|
||||
<string name="auto_focus_search_title">Búsqueda rápida</string>
|
||||
<string name="menu_delete_entry_history">Borrar historial</string>
|
||||
<string name="error_otp_digits">El token debe tener de %1$d a %2$d dígitos.</string>
|
||||
<string name="menu_delete_entry_history">Eliminar el historial</string>
|
||||
<string name="error_otp_digits">El token debe contener de %1$d a %2$d dígitos.</string>
|
||||
<string name="entry_attachments">Archivos adjuntos</string>
|
||||
<string name="entry_add_attachment">Adjuntar</string>
|
||||
<string name="entry_add_attachment">Añadir el archivo adjunto</string>
|
||||
<string name="content_description_credentials_information">Información de credenciales</string>
|
||||
<string name="database_opened">Base de datos abierta</string>
|
||||
<string name="education_add_attachment_title">Adjuntar</string>
|
||||
@@ -455,4 +457,94 @@
|
||||
<string name="warning_file_too_big">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.</string>
|
||||
<string name="recycle_bin_group_title">Grupo de la papelera de reciclaje</string>
|
||||
<string name="filter">Filtrar</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Elimina los archivos adjuntos contenidos en la base de datos pero no vinculados a una entrada</string>
|
||||
<string name="database_data_remove_unlinked_attachments_title">Eliminar los datos no vinculados</string>
|
||||
<string name="data">Datos</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">¿Borrar todas las claves de encriptación relacionadas con el reconocimiento de desbloqueo avanzado\?</string>
|
||||
<string name="advanced_unlock_timeout">Tiempo límite de desbloqueo avanzado</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Duración del uso de desbloqueo avanzado antes de borrar su contenido</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Expiración de desbloqueo avanzado</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">No almacenar ningún contenido encriptado para utilizar el desbloqueo avanzado</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Desbloqueo avanzado temporal</string>
|
||||
<string name="device_credential_unlock_enable_summary">Le permite usar la credenciales de su dispositivo para abrir la base de datos</string>
|
||||
<string name="device_credential_unlock_enable_title">Desbloqueo de las credenciales del dispositivo</string>
|
||||
<string name="advanced_unlock_tap_delete">Toque para eliminar las teclas de desbloqueo avanzadas</string>
|
||||
<string name="content">Contenido</string>
|
||||
<string name="clipboard_explanation_summary">Copiar los campos de entrada usando el portapapeles de su dispositivo</string>
|
||||
<string name="device_credential">Credenciales del dispositivo</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Introduzca la contraseña y luego haga clic en el botón \"Desbloqueo avanzado\".</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">No se pudo inicializar el indicador de desbloqueo avanzado.</string>
|
||||
<string name="advanced_unlock_scanning_error">Error de desbloqueo avanzado: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">No se pudo reconocer la impresión de desbloqueo avanzado</string>
|
||||
<string name="advanced_unlock_invalid_key">No se pudo leer la llave de desbloqueo avanzada. Por favor, bórrela y repita el procedimiento de reconocimiento de desbloqueo.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extraer la credencial de la base de datos con datos de desbloqueo avanzado</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Abrir la base de datos con reconocimiento de desbloqueo avanzado</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Advertencia: Aún debes recordar tu contraseña maestra si usas el reconocimiento de desbloqueo avanzado.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Reconocimiento de desbloqueo avanzado</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Abrir el indicador de desbloqueo avanzado para almacenar las credenciales</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Abrir el aviso de desbloqueo avanzado para desbloquear la base de datos</string>
|
||||
<string name="keystore_not_accessible">El almacén de claves no está debidamente inicializado.</string>
|
||||
<string name="biometric_security_update_required">Se requiere una actualización de la seguridad biométrica.</string>
|
||||
<string name="configure_biometric">No se ha inscrito ninguna credencial biométrica o del dispositivo.</string>
|
||||
<string name="warning_empty_keyfile_explanation">El contenido del archivo clave nunca debe modificarse y, en el mejor de los casos, debe contener datos generados al azar.</string>
|
||||
<string name="warning_empty_recycle_bin">¿Borrar permanentemente todos los nodos de la papelera de reciclaje\?</string>
|
||||
<string name="registration_mode">Modo de registro</string>
|
||||
<string name="save_mode">Modo de guardado</string>
|
||||
<string name="search_mode">Modo de búsqueda</string>
|
||||
<string name="contains_duplicate_uuid_procedure">¿Resolver el problema generando nuevos UUID para que los duplicados continúen\?</string>
|
||||
<string name="menu_keystore_remove_key">Borrar la clave de desbloqueo avanzado</string>
|
||||
<string name="error_field_name_already_exists">El nombre del campo ya existe.</string>
|
||||
<string name="error_registration_read_only">Guardar un nuevo elemento no está permitido en una base de datos de sólo lectura</string>
|
||||
<string name="settings_database_recommend_changing_master_key_title">Recomendar la renovación</string>
|
||||
<string name="max_history_size_summary">Limitar el tamaño del historial (en bytes) por entrada</string>
|
||||
<string name="max_history_items_summary">Limitar el número de elementos del historial por entrada</string>
|
||||
<string name="max_history_items_title">Número máximo</string>
|
||||
<string name="device_keyboard_setting_title">Configuración del teclado del dispositivo</string>
|
||||
<string name="database_custom_color_title">Color personalizado de la base de datos</string>
|
||||
<string name="settings_database_recommend_changing_master_key_summary">Recomendar cambiar la contraseña maestra (días)</string>
|
||||
<string name="notification">Notificación</string>
|
||||
<string name="hide_expired_entries_title">Ocultar las entradas expiradas</string>
|
||||
<string name="keyboard_search_share_title">Buscar información compartida</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="upload_attachment">Subir %1$s</string>
|
||||
<string name="education_setup_OTP_summary">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).</string>
|
||||
<string name="education_setup_OTP_title">Establecer la contaseña de un solo uso</string>
|
||||
<string name="education_advanced_unlock_summary">Vincule su contraseña con su credencial biométrica o del dispositivo escaneada para desbloquear rápidamente su base de datos.</string>
|
||||
<string name="education_advanced_unlock_title">Desbloqueo avanzado de la base de datos</string>
|
||||
<string name="autofill_read_only_save">No se permite guardar datos en una base de datos abierta como de sólo lectura.</string>
|
||||
<string name="autofill_block_restart">Reiniciar la aplicación que contiene el formulario para activar el bloqueo.</string>
|
||||
<string name="autofill_web_domain_blocklist_summary">Lista de bloqueo que impide el llenado automático de los dominios web</string>
|
||||
<string name="autofill_web_domain_blocklist_title">Lista de bloqueo de los dominios web</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Lista de bloqueo que impide el llenado automático de las aplicaciones</string>
|
||||
<string name="autofill_application_id_blocklist_title">Lista de bloqueo de las aplicaciones</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Pedir que se guarden los datos cuando se valide un formulario</string>
|
||||
<string name="autofill_ask_to_save_data_title">Pedir que se guarden los datos</string>
|
||||
<string name="autofill_save_search_info_summary">Intente guardar la información de la búsqueda cuando haga una selección de entrada manual</string>
|
||||
<string name="autofill_save_search_info_title">Guardar la información de la búsqueda</string>
|
||||
<string name="autofill_auto_search_summary">Sugerir automáticamente los resultados de la búsqueda desde el dominio web o el ID de la aplicación</string>
|
||||
<string name="autofill_auto_search_title">Búsqueda automática</string>
|
||||
<string name="autofill_close_database_summary">Cerrar la base de datos después de una selección de autocompletado</string>
|
||||
<string name="autofill_close_database_title">Cerrar la base de datos</string>
|
||||
<string name="enter">Entrar</string>
|
||||
<string name="backspace">Retroceder</string>
|
||||
<string name="select_entry">Seleccionar la entrada</string>
|
||||
<string name="back_to_previous_keyboard">Volver al teclado anterior</string>
|
||||
<string name="custom_fields">Campos personalizados</string>
|
||||
<string name="keyboard_previous_lock_summary">Cambiar automáticamente al teclado anterior después de bloquear la base de datos</string>
|
||||
<string name="keyboard_previous_lock_title">Bloquear la base de datos</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Cambiar automáticamente al teclado anterior después de ejecutar \"Acción de la tecla automática\"</string>
|
||||
<string name="keyboard_previous_fill_in_title">Acción de la tecla automática</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Cambiar automáticamente al teclado anterior en la pantalla de credenciales de la base de datos</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Pantalla de credenciales de la base de datos</string>
|
||||
<string name="keyboard_auto_go_action_title">Acción de la tecla automática</string>
|
||||
<string name="keyboard_save_search_info_summary">Intentar guardar la información compartida cuando hagas una selección de entrada manual</string>
|
||||
<string name="keyboard_save_search_info_title">Guardar información compartida</string>
|
||||
<string name="keyboard_search_share_summary">Buscar automáticamente la información compartida para llenar el teclado</string>
|
||||
<string name="show_uuid_summary">Muestra el UUID vinculado a una entrada</string>
|
||||
<string name="show_uuid_title">Mostrar UUID</string>
|
||||
<string name="error_rebuild_list">No es posible reconstruir adecuadamente la lista.</string>
|
||||
<string name="error_database_uri_null">La URI de la base de datos no puede ser recuperada.</string>
|
||||
</resources>
|
||||
@@ -550,4 +550,8 @@
|
||||
<string name="temp_advanced_unlock_enable_title">Déverrouillage avancé temporaire</string>
|
||||
<string name="advanced_unlock_tap_delete">Appuyez pour supprimer les clés de déverrouillage avancées</string>
|
||||
<string name="content">Contenu</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">Impossible de reconstruire correctement la liste.</string>
|
||||
<string name="error_database_uri_null">L\'URI de la base de données ne peut pas être récupéré.</string>
|
||||
</resources>
|
||||
@@ -525,4 +525,8 @@
|
||||
<string name="temp_advanced_unlock_enable_summary">Nemoj spremati šifrirani sadržaj za napredno otključavanje</string>
|
||||
<string name="content">Sadržaj</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Privremeno napredno otključavanje</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">Nije moguće ispravno obnoviti popis.</string>
|
||||
<string name="error_database_uri_null">URI baze podataka nije moguće dobiti.</string>
|
||||
</resources>
|
||||
@@ -401,10 +401,10 @@
|
||||
<string name="hide_broken_locations_title">Nascondi i collegamenti dei database corrotti</string>
|
||||
<string name="show_recent_files_summary">Mostra le posizioni dei database recenti</string>
|
||||
<string name="show_recent_files_title">Mostra file recenti</string>
|
||||
<string name="remember_keyfile_locations_title">Ricorda la posizione dei file chiave</string>
|
||||
<string name="remember_keyfile_locations_title">Ricorda posizione file chiave</string>
|
||||
<string name="remember_database_locations_summary">Ricorda la posizione dei database</string>
|
||||
<string name="remember_database_locations_title">Ricorda la posizione dei database</string>
|
||||
<string name="contains_duplicate_uuid_procedure">Per continuare, risolvi il problema generando nuovi UUID per i duplicati\?</string>
|
||||
<string name="remember_database_locations_title">Ricorda posizione database</string>
|
||||
<string name="contains_duplicate_uuid_procedure">Risolvi il problema generando nuovi UUID per i duplicati per continuare\?</string>
|
||||
<string name="error_create_database">Impossibile creare il file del database.</string>
|
||||
<string name="entry_add_attachment">Aggiungi allegato</string>
|
||||
<string name="discard">Scarta</string>
|
||||
@@ -428,7 +428,7 @@
|
||||
<string name="settings_database_recommend_changing_master_key_title">Rinnovo raccomandato</string>
|
||||
<string name="hide_expired_entries_summary">Le voci scadute non sono mostrate</string>
|
||||
<string name="hide_expired_entries_title">Nascondi le voci scadute</string>
|
||||
<string name="education_setup_OTP_summary">Imposta la gestione delle OTP (HOTP / TOTP) per generare un token richiesto per la 2FA.</string>
|
||||
<string name="education_setup_OTP_summary">Imposta la gestione della password usa e getta (HOTP/TOTP) per generare un token richiesto per l\'autenticazione a due fattori.</string>
|
||||
<string name="download_complete">Completato!</string>
|
||||
<string name="download_finalization">Finalizzazione…</string>
|
||||
<string name="download_progression">Avanzamento %1$d%%</string>
|
||||
@@ -451,7 +451,7 @@
|
||||
<string name="enable">Abilita</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">Richiedi il cambio della chiave principale la prossima volta (una volta)</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_title">Forza il rinnovo la prossima volta</string>
|
||||
<string name="settings_database_force_changing_master_key_summary">Richiedi il cambio della master key (giorni)</string>
|
||||
<string name="settings_database_force_changing_master_key_summary">Richiedi la modifica della chiave principale (giorni)</string>
|
||||
<string name="lock_database_show_button_summary">Mostra il bottone di blocco nell\'interfaccia utente</string>
|
||||
<string name="lock_database_show_button_title">Mostra il bottone di blocco</string>
|
||||
<string name="autofill_preference_title">Impostazioni dell\'autocompletamento</string>
|
||||
@@ -540,8 +540,12 @@
|
||||
<string name="device_credential">Credenziali del dispositivo</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Inserisci la password, quindi clicca sull\'icona \"Sblocco avanzato\".</string>
|
||||
<string name="advanced_unlock_scanning_error">Errore sblocco avanzato: %1$s</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Apri il database autenticando con lo sblocco avanzato</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Apri il database con lo sblocco avanzato</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Autentica con lo sblocco avanzato per sbloccare il database</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Autentica con lo sblocco avanzato per salvare le credenziali</string>
|
||||
<string name="menu_keystore_remove_key">Elimina chiave di sblocco avanzato</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">Non è possibile ricostruire la lista correttamente.</string>
|
||||
<string name="error_database_uri_null">Non è stato recuperato l\'indirizzo del database.</string>
|
||||
</resources>
|
||||
@@ -541,4 +541,7 @@
|
||||
<string name="temp_advanced_unlock_enable_title">一時的な高度なロック解除</string>
|
||||
<string name="advanced_unlock_tap_delete">タップして高度なロック解除用の鍵を削除する</string>
|
||||
<string name="content">コンテンツ</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_database_uri_null">データベースのURIが見つかりません。</string>
|
||||
</resources>
|
||||
@@ -388,4 +388,12 @@
|
||||
<string name="registration_mode">രജിസ്ട്രേഷൻ മോഡ്</string>
|
||||
<string name="search_mode">തിരയൽ മോഡ്</string>
|
||||
<string name="error_registration_read_only">read-only ഡാറ്റാബേസിൽ പുതിയ ഒരു ഇനം സംരക്ഷിക്കാൻ കഴിയില്ല</string>
|
||||
<string name="education_advanced_unlock_title">വിപുലമായ ഡാറ്റാബേസ് അൺലോക്ക്</string>
|
||||
<string name="select_entry">എൻട്രി തിരഞ്ഞെടുക്കുക</string>
|
||||
<string name="custom_fields">ഇഷ്ടാനുസൃത ഫീൽഡുകൾ</string>
|
||||
<string name="keyboard_keys_category">കീകൾ</string>
|
||||
<string name="notification">അറിയിപ്പ്</string>
|
||||
<string name="settings_database_recommend_changing_master_key_title">പുതുക്കൽ ശുപാർശ ചെയ്യുക</string>
|
||||
<string name="content">ഉള്ളടക്കം</string>
|
||||
<string name="device_credential">ഉപകരണ ക്രെഡൻഷ്യൽ</string>
|
||||
</resources>
|
||||
@@ -529,10 +529,16 @@
|
||||
<string name="menu_keystore_remove_key">Usuń zaawansowany klucz odblokowujący</string>
|
||||
<string name="education_advanced_unlock_summary">Połącz swoje hasło ze zeskanowanymi danymi biometrycznymi lub danymi logowania urządzenia, aby szybko odblokować bazę danych.</string>
|
||||
<string name="education_advanced_unlock_title">Zaawansowane odblokowywanie bazy danych</string>
|
||||
<string name="advanced_unlock_timeout">Zaawansowany limit czasu odblokowania</string>
|
||||
<string name="advanced_unlock_timeout">Limit czasu zaawansowanego odblokowywania</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Czas trwania zaawansowanego odblokowywania przed usunięciem jego zawartości</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Wygaśnięcie zaawansowanego odblokowania</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Nie przechowuj żadnych zaszyfrowanych treści, aby korzystać z zaawansowanego odblokowywania</string>
|
||||
<string name="advanced_unlock_tap_delete">Naciśnij, aby usunąć zaawansowane klucze odblokowujące</string>
|
||||
<string name="content">Zawartość</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Otwórz bazę danych z zaawansowanym rozpoznawaniem odblokowania</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="advanced_unlock_scanning_error">Błąd zaawansowanego odblokowywania: %1$s</string>
|
||||
<string name="error_rebuild_list">Nie można poprawnie odbudować listy.</string>
|
||||
<string name="error_database_uri_null">Nie można pobrać identyfikatora URI bazy danych.</string>
|
||||
</resources>
|
||||
@@ -519,10 +519,10 @@
|
||||
<string name="advanced_unlock_not_recognized">Невозможно распознать расширенную разблокировку</string>
|
||||
<string name="advanced_unlock_invalid_key">Невозможно прочитать ключ расширенной разблокировки. Удалите его и повторите процедуру распознавания разблокировки.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Извлекать учётные данные базы с использованием расширенной разблокировки</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Открывать базу с расширенным распознаванием разблокировки</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Открыть базу с расширенным распознаванием разблокировки</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Предупреждение: даже при использовании расширенной разблокировки вам всё равно необходимо помнить главный пароль.</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Открывать запрос расширенной разблокировки для сохранения учётных данных</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Открывать запрос расширенной разблокировки для разблокировки базы</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Открыть запрос расширенной разблокировки для сохранения учётных данных</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Открыть запрос расширенной разблокировки для разблокировки базы</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки\?</string>
|
||||
<string name="advanced_unlock_scanning_error">Ошибка расширенной разблокировки: %1$s</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Распознавание расширенной разблокировки</string>
|
||||
@@ -541,4 +541,8 @@
|
||||
<string name="temp_advanced_unlock_enable_summary">Не сохранять зашифрованное содержимое для использования расширенной разблокировки</string>
|
||||
<string name="advanced_unlock_tap_delete">Нажмите, чтобы удалить ключи расширенной разблокировки</string>
|
||||
<string name="content">Содержимое</string>
|
||||
<string name="kdf_Argon2id">Argon2ID</string>
|
||||
<string name="kdf_Argon2d">Argon2D</string>
|
||||
<string name="error_database_uri_null">Невозможно получить URI базы.</string>
|
||||
<string name="error_rebuild_list">Невозможно правильно перестроить список.</string>
|
||||
</resources>
|
||||
@@ -525,4 +525,8 @@
|
||||
<string name="temp_advanced_unlock_enable_title">Geçici gelişmiş kilit açma</string>
|
||||
<string name="advanced_unlock_tap_delete">Gelişmiş kilit açma anahtarlarını silmek için dokunun</string>
|
||||
<string name="content">İçerik</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">Liste düzgün şekilde yeniden oluşturulamıyor.</string>
|
||||
<string name="error_database_uri_null">Veri tabanı URI\'si alınamıyor.</string>
|
||||
</resources>
|
||||
@@ -541,4 +541,8 @@
|
||||
<string name="temp_advanced_unlock_enable_title">Тимчасове розширене розблокування</string>
|
||||
<string name="advanced_unlock_tap_delete">Торкнутися, щоб видалити клавіші розширеного розблокування</string>
|
||||
<string name="content">Вміст</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">Не вдалося належним чином відновити список.</string>
|
||||
<string name="error_database_uri_null">Неможливо отримати URI бази даних.</string>
|
||||
</resources>
|
||||
22
app/src/main/res/values-v30/donottranslate.xml
Normal file
22
app/src/main/res/values-v30/donottranslate.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<bool name="autofill_inline_suggestions_default" translatable="false">true</bool>
|
||||
</resources>
|
||||
@@ -541,4 +541,8 @@
|
||||
<string name="temp_advanced_unlock_enable_title">临时性高级解锁</string>
|
||||
<string name="advanced_unlock_tap_delete">点击删除高级解锁密钥</string>
|
||||
<string name="content">内容</string>
|
||||
<string name="kdf_Argon2id">Argon2id</string>
|
||||
<string name="kdf_Argon2d">Argon2d</string>
|
||||
<string name="error_rebuild_list">无法正确地重建列表。</string>
|
||||
<string name="error_database_uri_null">无法检索数据库 URI 。</string>
|
||||
</resources>
|
||||
@@ -151,6 +151,8 @@
|
||||
<bool name="autofill_close_database_default" translatable="false">false</bool>
|
||||
<string name="autofill_auto_search_key" translatable="false">autofill_auto_search_key</string>
|
||||
<bool name="autofill_auto_search_default" translatable="false">true</bool>
|
||||
<string name="autofill_inline_suggestions_key" translatable="false">autofill_inline_suggestions_key</string>
|
||||
<bool name="autofill_inline_suggestions_default" translatable="false">false</bool>
|
||||
<string name="autofill_save_search_info_key" translatable="false">autofill_save_search_info_key</string>
|
||||
<bool name="autofill_save_search_info_default" translatable="false">true</bool>
|
||||
<string name="autofill_ask_to_save_data_key" translatable="false">autofill_ask_to_save_data_key</string>
|
||||
|
||||
@@ -134,6 +134,8 @@
|
||||
<string name="error_string_type">This text does not match the requested item.</string>
|
||||
<string name="error_registration_read_only">Saving a new item is not allowed in a read-only database</string>
|
||||
<string name="error_field_name_already_exists">The field name already exists.</string>
|
||||
<string name="error_database_uri_null">Database URI cannot be retrieved.</string>
|
||||
<string name="error_rebuild_list">Unable to properly rebuild the list.</string>
|
||||
<string name="field_name">Field name</string>
|
||||
<string name="field_value">Field value</string>
|
||||
<string name="file_not_found_content">Could not find file. Try reopening it from your file browser.</string>
|
||||
@@ -183,6 +185,7 @@
|
||||
<string name="menu_hide_password">Hide password</string>
|
||||
<string name="menu_lock">Lock database</string>
|
||||
<string name="menu_save_database">Save database</string>
|
||||
<string name="menu_reload_database">Reload database</string>
|
||||
<string name="menu_open">Open</string>
|
||||
<string name="menu_search">Search</string>
|
||||
<string name="menu_showpass">Show password</string>
|
||||
@@ -270,6 +273,9 @@
|
||||
<string name="warning_sure_remove_data">Remove this data anyway?</string>
|
||||
<string name="warning_empty_keyfile">It is not recommended to add an empty keyfile.</string>
|
||||
<string name="warning_empty_keyfile_explanation">The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.</string>
|
||||
<string name="warning_database_info_changed">The information contained in your database file has been modified outside the app.</string>
|
||||
<string name="warning_database_info_changed_options">Overwrite the external modifications by saving the database or reload it with the latest changes.</string>
|
||||
<string name="warning_database_revoked">Access to the file revoked by the file manager, close the database and reopen it from its location.</string>
|
||||
<string name="version_label">Version %1$s</string>
|
||||
<string name="build_label">Build %1$s</string>
|
||||
<string name="configure_biometric">No biometric or device credential is enrolled.</string>
|
||||
@@ -426,6 +432,8 @@
|
||||
<string name="autofill_close_database_summary">Close the database after an autofill selection</string>
|
||||
<string name="autofill_auto_search_title">Auto search</string>
|
||||
<string name="autofill_auto_search_summary">Automatically suggest search results from the web domain or application ID</string>
|
||||
<string name="autofill_inline_suggestions_title">Inline suggestions</string>
|
||||
<string name="autofill_inline_suggestions_summary">Attempt to display autofill suggestions directly from a compatible keyboard</string>
|
||||
<string name="autofill_save_search_info_title">Save search info</string>
|
||||
<string name="autofill_save_search_info_summary">Try to save search information when making a manual entry selection</string>
|
||||
<string name="autofill_ask_to_save_data_title">Ask to save data</string>
|
||||
@@ -437,6 +445,7 @@
|
||||
<string name="autofill_block">Block autofill</string>
|
||||
<string name="autofill_block_restart">Restart the app containing the form to activate the blocking.</string>
|
||||
<string name="autofill_read_only_save">Data save is not allowed for a database opened as read-only.</string>
|
||||
<string name="autofill_inline_suggestions_keyboard">Autofill suggestions added.</string>
|
||||
<string name="allow_no_password_title">Allow no master key</string>
|
||||
<string name="allow_no_password_summary">Allows tapping the \"Open\" button if no credentials are selected</string>
|
||||
<string name="delete_entered_password_title">Delete password</string>
|
||||
|
||||
@@ -465,6 +465,7 @@
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
|
||||
@@ -23,7 +23,9 @@ Settings Activity. This is pointed to in the service's meta-data in the applicat
|
||||
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:targetApi="p"
|
||||
android:settingsActivity="com.kunzisoft.keepass.settings.AutofillSettingsActivity" >
|
||||
android:settingsActivity="com.kunzisoft.keepass.settings.AutofillSettingsActivity"
|
||||
android:supportsInlineSuggestions="true"
|
||||
tools:ignore="UnusedAttribute">
|
||||
<compatibility-package
|
||||
android:name="com.amazon.cloud9"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
android:title="@string/autofill_auto_search_title"
|
||||
android:summary="@string/autofill_auto_search_summary"
|
||||
android:defaultValue="@bool/autofill_auto_search_default"/>
|
||||
<SwitchPreference
|
||||
android:key="@string/autofill_inline_suggestions_key"
|
||||
android:title="@string/autofill_inline_suggestions_title"
|
||||
android:summary="@string/autofill_inline_suggestions_summary"
|
||||
android:defaultValue="@bool/autofill_inline_suggestions_default"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/save">
|
||||
|
||||
@@ -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()
|
||||
|
||||
1
fastlane/metadata/android/en-US/changelogs/50.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/50.txt
Normal file
@@ -0,0 +1 @@
|
||||
* Fix KeyFile bug #820
|
||||
2
fastlane/metadata/android/en-US/changelogs/51.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/51.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
* Fix small bugs
|
||||
* Remove write permission since Android 10
|
||||
2
fastlane/metadata/android/en-US/changelogs/52.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/52.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
* Fix specific attachments with kdbx3.1 databases #828
|
||||
* Fix small bugs
|
||||
8
fastlane/metadata/android/en-US/changelogs/53.txt
Normal file
8
fastlane/metadata/android/en-US/changelogs/53.txt
Normal file
@@ -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
|
||||
1
fastlane/metadata/android/fr-FR/changelogs/50.txt
Normal file
1
fastlane/metadata/android/fr-FR/changelogs/50.txt
Normal file
@@ -0,0 +1 @@
|
||||
* Correction du bug de fichier de clé #820
|
||||
2
fastlane/metadata/android/fr-FR/changelogs/51.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/51.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
* Correction de petits bugs
|
||||
* Suppression des permissions d'écriture à partir d'Android 10
|
||||
2
fastlane/metadata/android/fr-FR/changelogs/52.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/52.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
* Correction des pièces jointes spécifiques avec les bases kdbx3.1 #828
|
||||
* Correction de petits bugs
|
||||
8
fastlane/metadata/android/fr-FR/changelogs/53.txt
Normal file
8
fastlane/metadata/android/fr-FR/changelogs/53.txt
Normal file
@@ -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
|
||||
@@ -1,7 +1,7 @@
|
||||
<b>Multi-format KeePass password manager</b>, 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 <b>faster development</b>, <b>better service</b>, and you contribute to the creation of <b>open source softwares without advertising</b>.
|
||||
<i>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.</i>
|
||||
<i>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.</i>
|
||||
|
||||
<b>Features</b>
|
||||
- Create database files / entries and groups.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<b>Gestionnaire de mots de passe KeePass multiformats</b>, 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 <b>un développement plus rapide</b>, <b>un meilleur service</b> et vous contribuez à la création de <b>logiciels open source sans publicité</b>.
|
||||
<i>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.</i>
|
||||
<i>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.</i>
|
||||
|
||||
<b>Fonctionnalités</b>
|
||||
- Création de bases de données / entrées et groupes.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<b>複数の形式に対応する KeePass パスワード マネージャー</b>。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。
|
||||
|
||||
この pro バージョンは開発中です。購入することで<b>開発の加速</b>と<b>サービスの改善</b>を支援し、<b>広告なしのオープンソース ソフトウェア</b>の作成に貢献できます。
|
||||
<i>現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。よく利用されるサイトやサービスの接続と同期を楽にする要素を統合することが予定されています。</i>
|
||||
<i>現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。一般的に使われている不自由なサイトやサービスに関連する要素を統合することを計画しています。</i>
|
||||
|
||||
<b>機能</b>
|
||||
- データベースファイル / エントリー・グループの作成
|
||||
|
||||
Reference in New Issue
Block a user