mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2928b7daa3 | ||
|
|
3a55dea276 | ||
|
|
2a25213d66 | ||
|
|
035ffd8135 | ||
|
|
b040487f1f | ||
|
|
6fc821aecf | ||
|
|
cdceb1fb6f | ||
|
|
07d185913d | ||
|
|
f2a245a9c8 | ||
|
|
33338f4759 | ||
|
|
f7a4370b29 | ||
|
|
77b7afedda | ||
|
|
caa13039e5 | ||
|
|
02845d93ed | ||
|
|
9ef4695cc7 | ||
|
|
d619e089c0 | ||
|
|
3c50348a79 | ||
|
|
167ea3b82b | ||
|
|
9eda3e62f7 | ||
|
|
99c4319b51 | ||
|
|
790b25db65 | ||
|
|
97d4972f9a | ||
|
|
8e6853756f | ||
|
|
6d3aae187b | ||
|
|
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 | ||
|
|
062a9852e5 | ||
|
|
9a6a709746 | ||
|
|
5ab3cf985a | ||
|
|
e18b3436c9 | ||
|
|
fcb1b5ae6b |
26
CHANGELOG
26
CHANGELOG
@@ -1,6 +1,28 @@
|
|||||||
KeePassDX(2.9.7)
|
KeePassDX(2.9.11)
|
||||||
|
* Add Keyfile XML version 2 (fix hex) #844
|
||||||
|
* Fix hex Keyfile #861
|
||||||
|
|
||||||
|
KeePassDX(2.9.10)
|
||||||
|
* Try to fix autofill #852
|
||||||
|
* Fix database change dialog displayed too often #853
|
||||||
|
|
||||||
|
KeePassDX(2.9.9)
|
||||||
|
* Detect file changes and reload database #794
|
||||||
|
* Inline suggestions autofill with compatible keyboard (Android R) #827
|
||||||
|
* Add Keyfile XML version 2 #844
|
||||||
|
* Fix binaries of 64 bytes #835
|
||||||
|
* Special search in title fields #830
|
||||||
|
* Priority to OTP button in notifications #845
|
||||||
|
* Fix OTP generation for long secret key #848
|
||||||
|
* Fix small bugs #849
|
||||||
|
|
||||||
|
KeePassDX(2.9.8)
|
||||||
|
* Fix specific attachments with kdbx3.1 databases #828
|
||||||
|
* Fix small bugs
|
||||||
|
|
||||||
|
KeePassDX(2.9.7)
|
||||||
|
* Remove write permission since Android 10 #823
|
||||||
* Fix small bugs
|
* Fix small bugs
|
||||||
* Remove write permission since Android 10
|
|
||||||
|
|
||||||
KeePassDX(2.9.6)
|
KeePassDX(2.9.6)
|
||||||
* Fix KeyFile bug #820
|
* Fix KeyFile bug #820
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ apply plugin: 'kotlin-kapt'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
buildToolsVersion '30.0.2'
|
buildToolsVersion '30.0.3'
|
||||||
ndkVersion '21.3.6528147'
|
ndkVersion '21.3.6528147'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.kunzisoft.keepass"
|
applicationId "com.kunzisoft.keepass"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode = 51
|
versionCode = 55
|
||||||
versionName = "2.9.7"
|
versionName = "2.9.11"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||||
@@ -92,7 +92,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def room_version = "2.2.5"
|
def room_version = "2.2.6"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
@@ -110,6 +110,8 @@ dependencies {
|
|||||||
// Database
|
// Database
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
// Autofill
|
||||||
|
implementation "androidx.autofill:autofill:1.1.0-rc01"
|
||||||
// Crypto
|
// Crypto
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
|
||||||
// Time
|
// Time
|
||||||
@@ -121,7 +123,7 @@ dependencies {
|
|||||||
// Apache Commons Collections
|
// Apache Commons Collections
|
||||||
implementation 'commons-collections:commons-collections:3.2.2'
|
implementation 'commons-collections:commons-collections:3.2.2'
|
||||||
// Apache Commons Codec
|
// Apache Commons Codec
|
||||||
implementation 'commons-codec:commons-codec:1.14'
|
implementation 'commons-codec:commons-codec:1.15'
|
||||||
// Icon pack
|
// Icon pack
|
||||||
implementation project(path: ':icon-pack-classic')
|
implementation project(path: ':icon-pack-classic')
|
||||||
implementation project(path: ':icon-pack-material')
|
implementation project(path: ':icon-pack-material')
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import android.content.Intent
|
|||||||
import android.content.IntentSender
|
import android.content.IntentSender
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.inputmethod.InlineSuggestionsRequest
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
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.autofill.KeeAutofillService
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
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.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
class AutofillLauncherActivity : AppCompatActivity() {
|
class AutofillLauncherActivity : AppCompatActivity() {
|
||||||
@@ -84,9 +85,9 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun launchSelection(searchInfo: SearchInfo) {
|
private fun launchSelection(searchInfo: SearchInfo) {
|
||||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
// 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)
|
setResult(Activity.RESULT_CANCELED)
|
||||||
finish()
|
finish()
|
||||||
} else if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId,
|
} else if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId,
|
||||||
@@ -105,21 +106,21 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
|||||||
searchInfo,
|
searchInfo,
|
||||||
{ items ->
|
{ items ->
|
||||||
// Items found
|
// Items found
|
||||||
AutofillHelper.buildResponse(this, items)
|
AutofillHelper.buildResponseAndSetResult(this, items)
|
||||||
finish()
|
finish()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Show the database UI to select the entry
|
// Show the database UI to select the entry
|
||||||
GroupActivity.launchForAutofillResult(this,
|
GroupActivity.launchForAutofillResult(this,
|
||||||
readOnly,
|
readOnly,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo,
|
searchInfo,
|
||||||
false)
|
false)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// If database not open
|
// If database not open
|
||||||
FileDatabaseSelectActivity.launchForAutofillResult(this,
|
FileDatabaseSelectActivity.launchForAutofillResult(this,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo)
|
searchInfo)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -196,7 +197,8 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
|||||||
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
|
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
|
||||||
|
|
||||||
fun getAuthIntentSenderForSelection(context: Context,
|
fun getAuthIntentSenderForSelection(context: Context,
|
||||||
searchInfo: SearchInfo? = null): IntentSender {
|
searchInfo: SearchInfo? = null,
|
||||||
|
inlineSuggestionsRequest: InlineSuggestionsRequest? = null): IntentSender {
|
||||||
return PendingIntent.getActivity(context, 0,
|
return PendingIntent.getActivity(context, 0,
|
||||||
// Doesn't work with Parcelable (don't know why?)
|
// Doesn't work with Parcelable (don't know why?)
|
||||||
Intent(context, AutofillLauncherActivity::class.java).apply {
|
Intent(context, AutofillLauncherActivity::class.java).apply {
|
||||||
@@ -205,6 +207,11 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
|||||||
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
|
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
|
||||||
putExtra(KEY_SEARCH_SCHEME, it.webScheme)
|
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
|
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|||||||
import com.google.android.material.appbar.CollapsingToolbarLayout
|
import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
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.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||||
import com.kunzisoft.keepass.database.element.Attachment
|
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.AttachmentFileNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
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_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.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||||
|
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||||
@@ -151,6 +154,10 @@ class EntryActivity : LockingActivity() {
|
|||||||
if (result.isSuccess)
|
if (result.isSuccess)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
ACTION_DATABASE_RELOAD_TASK -> {
|
||||||
|
// Close the current activity
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
coordinatorLayout?.showActionError(result)
|
coordinatorLayout?.showActionError(result)
|
||||||
}
|
}
|
||||||
@@ -199,8 +206,7 @@ class EntryActivity : LockingActivity() {
|
|||||||
// Refresh Menu
|
// Refresh Menu
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
val entryInfo = entry.getEntryInfo(Database.getInstance())
|
val entryInfo = entry.getEntryInfo(mDatabase)
|
||||||
|
|
||||||
// Manage entry copy to start notification if allowed
|
// Manage entry copy to start notification if allowed
|
||||||
if (mFirstLaunchOfActivity) {
|
if (mFirstLaunchOfActivity) {
|
||||||
// Manage entry to launch copying notification if allowed
|
// Manage entry to launch copying notification if allowed
|
||||||
@@ -232,23 +238,21 @@ class EntryActivity : LockingActivity() {
|
|||||||
|
|
||||||
private fun fillEntryDataInContentsView(entry: Entry) {
|
private fun fillEntryDataInContentsView(entry: Entry) {
|
||||||
|
|
||||||
val database = Database.getInstance()
|
val entryInfo = entry.getEntryInfo(mDatabase)
|
||||||
database.startManageEntry(entry)
|
|
||||||
// Assign title icon
|
// Assign title icon
|
||||||
titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor)
|
titleIconView?.assignDatabaseIcon(mDatabase!!.drawFactory, entryInfo.icon, iconColor)
|
||||||
|
|
||||||
// Assign title text
|
// Assign title text
|
||||||
val entryTitle = entry.title
|
val entryTitle = entryInfo.title
|
||||||
collapsingToolbarLayout?.title = entryTitle
|
collapsingToolbarLayout?.title = entryTitle
|
||||||
toolbar?.title = entryTitle
|
toolbar?.title = entryTitle
|
||||||
|
|
||||||
// Assign basic fields
|
// Assign basic fields
|
||||||
entryContentsView?.assignUserName(entry.username) {
|
entryContentsView?.assignUserName(entryInfo.username) {
|
||||||
database.startManageEntry(entry)
|
clipboardHelper?.timeoutCopyToClipboard(entryInfo.username,
|
||||||
clipboardHelper?.timeoutCopyToClipboard(entry.username,
|
|
||||||
getString(R.string.copy_field,
|
getString(R.string.copy_field,
|
||||||
getString(R.string.entry_user_name)))
|
getString(R.string.entry_user_name)))
|
||||||
database.stopManageEntry(entry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
|
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
|
||||||
@@ -278,11 +282,9 @@ class EntryActivity : LockingActivity() {
|
|||||||
|
|
||||||
val onPasswordCopyClickListener: View.OnClickListener? = if (allowCopyPasswordAndProtectedFields) {
|
val onPasswordCopyClickListener: View.OnClickListener? = if (allowCopyPasswordAndProtectedFields) {
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
database.startManageEntry(entry)
|
clipboardHelper?.timeoutCopyToClipboard(entryInfo.password,
|
||||||
clipboardHelper?.timeoutCopyToClipboard(entry.password,
|
|
||||||
getString(R.string.copy_field,
|
getString(R.string.copy_field,
|
||||||
getString(R.string.entry_password)))
|
getString(R.string.entry_password)))
|
||||||
database.stopManageEntry(entry)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If dialog not already shown
|
// If dialog not already shown
|
||||||
@@ -292,44 +294,46 @@ class EntryActivity : LockingActivity() {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entryContentsView?.assignPassword(entry.password,
|
entryContentsView?.assignPassword(entryInfo.password,
|
||||||
allowCopyPasswordAndProtectedFields,
|
allowCopyPasswordAndProtectedFields,
|
||||||
onPasswordCopyClickListener)
|
onPasswordCopyClickListener)
|
||||||
|
|
||||||
//Assign OTP field
|
//Assign OTP field
|
||||||
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
|
entry.getOtpElement()?.let { otpElement ->
|
||||||
View.OnClickListener {
|
entryContentsView?.assignOtp(otpElement, entryProgress) {
|
||||||
entry.getOtpElement()?.let { otpElement ->
|
clipboardHelper?.timeoutCopyToClipboard(
|
||||||
clipboardHelper?.timeoutCopyToClipboard(
|
otpElement.token,
|
||||||
otpElement.token,
|
getString(R.string.copy_field, getString(R.string.entry_otp))
|
||||||
getString(R.string.copy_field, getString(R.string.entry_otp))
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
entryContentsView?.assignURL(entry.url)
|
entryContentsView?.assignURL(entryInfo.url)
|
||||||
entryContentsView?.assignNotes(entry.notes)
|
entryContentsView?.assignNotes(entryInfo.notes)
|
||||||
|
|
||||||
// Assign custom fields
|
// Assign custom fields
|
||||||
if (mDatabase?.allowEntryCustomFields() == true) {
|
if (mDatabase?.allowEntryCustomFields() == true) {
|
||||||
entryContentsView?.clearExtraFields()
|
entryContentsView?.clearExtraFields()
|
||||||
entry.getExtraFields().forEach { field ->
|
entryInfo.customFields.forEach { field ->
|
||||||
val label = field.name
|
val label = field.name
|
||||||
val value = field.protectedValue
|
// OTP field is already managed in dedicated view
|
||||||
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
if (label != OtpEntryFields.OTP_TOKEN_FIELD) {
|
||||||
if (allowCopyProtectedField) {
|
val value = field.protectedValue
|
||||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
|
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
||||||
clipboardHelper?.timeoutCopyToClipboard(
|
if (allowCopyProtectedField) {
|
||||||
value.toString(),
|
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
|
||||||
getString(R.string.copy_field, label)
|
clipboardHelper?.timeoutCopyToClipboard(
|
||||||
)
|
value.toString(),
|
||||||
}
|
getString(R.string.copy_field, label)
|
||||||
} else {
|
)
|
||||||
// If dialog not already shown
|
}
|
||||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
|
||||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
|
|
||||||
} else {
|
} 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)
|
entryContentsView?.setHiddenProtectedValue(!mShowPassword)
|
||||||
|
|
||||||
// Manage attachments
|
// Manage attachments
|
||||||
mDatabase?.binaryPool?.let { binaryPool ->
|
entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
|
||||||
entryContentsView?.assignAttachments(entry.getAttachments(binaryPool).toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
|
createDocument(this, attachmentItem.name)?.let { requestCode ->
|
||||||
createDocument(this, attachmentItem.name)?.let { requestCode ->
|
mAttachmentsToDownload[requestCode] = attachmentItem
|
||||||
mAttachmentsToDownload[requestCode] = attachmentItem
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign dates
|
// Assign dates
|
||||||
entryContentsView?.assignCreationDate(entry.creationTime)
|
entryContentsView?.assignCreationDate(entryInfo.creationTime)
|
||||||
entryContentsView?.assignModificationDate(entry.lastModificationTime)
|
entryContentsView?.assignModificationDate(entryInfo.modificationTime)
|
||||||
entryContentsView?.assignLastAccessDate(entry.lastAccessTime)
|
entryContentsView?.setExpires(entryInfo.expires, entryInfo.expiryTime)
|
||||||
entryContentsView?.setExpires(entry.isCurrentlyExpires)
|
|
||||||
if (entry.expires) {
|
|
||||||
entryContentsView?.assignExpiresDate(entry.expiryTime)
|
|
||||||
} else {
|
|
||||||
entryContentsView?.assignExpiresDate(getString(R.string.never))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manage history
|
// Manage history
|
||||||
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
|
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
|
||||||
@@ -369,8 +365,6 @@ class EntryActivity : LockingActivity() {
|
|||||||
|
|
||||||
// Assign special data
|
// Assign special data
|
||||||
entryContentsView?.assignUUID(entry.nodeId.id)
|
entryContentsView?.assignUUID(entry.nodeId.id)
|
||||||
|
|
||||||
database.stopManageEntry(entry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
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_save_database)?.isVisible = false
|
||||||
menu.findItem(R.id.menu_edit)?.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)
|
val gotoUrl = menu.findItem(R.id.menu_goto_url)
|
||||||
gotoUrl?.apply {
|
gotoUrl?.apply {
|
||||||
@@ -501,6 +498,9 @@ class EntryActivity : LockingActivity() {
|
|||||||
R.id.menu_save_database -> {
|
R.id.menu_save_database -> {
|
||||||
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
|
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)
|
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.activities
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.DatePickerDialog
|
import android.app.DatePickerDialog
|
||||||
import android.app.TimePickerDialog
|
import android.app.TimePickerDialog
|
||||||
import android.app.assist.AssistStructure
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
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.helpers.SelectFileHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||||
|
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.database.element.*
|
import com.kunzisoft.keepass.database.element.*
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
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.ClipboardEntryNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
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_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.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
@@ -335,6 +336,10 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
Log.e(TAG, "Unable to retrieve entry after database action", e)
|
Log.e(TAG, "Unable to retrieve entry after database action", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ACTION_DATABASE_RELOAD_TASK -> {
|
||||||
|
// Close the current activity
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
coordinatorLayout?.showActionError(result)
|
coordinatorLayout?.showActionError(result)
|
||||||
}
|
}
|
||||||
@@ -361,7 +366,7 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
// Build Autofill response with the entry selected
|
// Build Autofill response with the entry selected
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
mDatabase?.let { database ->
|
mDatabase?.let { database ->
|
||||||
AutofillHelper.buildResponse(this@EntryEditActivity,
|
AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
|
||||||
entry.getEntryInfo(database))
|
entry.getEntryInfo(database))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -479,8 +484,12 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
|
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
|
||||||
verifyNameField(newField) {
|
if (oldField.name.equals(newField.name, true)) {
|
||||||
entryEditFragment?.replaceExtraField(oldField, newField)
|
entryEditFragment?.replaceExtraField(oldField, newField)
|
||||||
|
} else {
|
||||||
|
verifyNameField(newField) {
|
||||||
|
entryEditFragment?.replaceExtraField(oldField, newField)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,13 +619,7 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
|
MenuUtil.contributionMenuInflater(menuInflater, 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)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,9 +676,6 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.menu_save_database -> {
|
|
||||||
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
|
|
||||||
}
|
|
||||||
R.id.menu_contribute -> {
|
R.id.menu_contribute -> {
|
||||||
MenuUtil.onContributionItemSelected(this)
|
MenuUtil.onContributionItemSelected(this)
|
||||||
return true
|
return true
|
||||||
@@ -909,7 +909,7 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
fun launchForAutofillResult(activity: Activity,
|
fun launchForAutofillResult(activity: Activity,
|
||||||
assistStructure: AssistStructure,
|
autofillComponent: AutofillComponent,
|
||||||
group: Group,
|
group: Group,
|
||||||
searchInfo: SearchInfo? = null) {
|
searchInfo: SearchInfo? = null) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
@@ -917,7 +917,7 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
intent.putExtra(KEY_PARENT, group.nodeId)
|
intent.putExtra(KEY_PARENT, group.nodeId)
|
||||||
AutofillHelper.startActivityForAutofillResult(activity,
|
AutofillHelper.startActivityForAutofillResult(activity,
|
||||||
intent,
|
intent,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo)
|
searchInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.assist.AssistStructure
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
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.activities.selection.SpecialModeActivity
|
||||||
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
|
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
@@ -501,11 +501,11 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
|||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
fun launchForAutofillResult(activity: Activity,
|
fun launchForAutofillResult(activity: Activity,
|
||||||
assistStructure: AssistStructure,
|
autofillComponent: AutofillComponent,
|
||||||
searchInfo: SearchInfo? = null) {
|
searchInfo: SearchInfo? = null) {
|
||||||
AutofillHelper.startActivityForAutofillResult(activity,
|
AutofillHelper.startActivityForAutofillResult(activity,
|
||||||
Intent(activity, FileDatabaseSelectActivity::class.java),
|
Intent(activity, FileDatabaseSelectActivity::class.java),
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo)
|
searchInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package com.kunzisoft.keepass.activities
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.app.assist.AssistStructure
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
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.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||||
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
|
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
|
||||||
|
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
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_CREATE_GROUP_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_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_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_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
|
||||||
@@ -228,10 +229,10 @@ class GroupActivity : LockingActivity(),
|
|||||||
currentGroup, searchInfo)
|
currentGroup, searchInfo)
|
||||||
onLaunchActivitySpecialMode()
|
onLaunchActivitySpecialMode()
|
||||||
},
|
},
|
||||||
{ searchInfo, assistStructure ->
|
{ searchInfo, autofillComponent ->
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
EntryEditActivity.launchForAutofillResult(this@GroupActivity,
|
EntryEditActivity.launchForAutofillResult(this@GroupActivity,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
currentGroup, searchInfo)
|
currentGroup, searchInfo)
|
||||||
onLaunchActivitySpecialMode()
|
onLaunchActivitySpecialMode()
|
||||||
} else {
|
} else {
|
||||||
@@ -342,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)
|
coordinatorLayout?.showActionError(result)
|
||||||
@@ -665,7 +672,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
// Build response with the entry selected
|
// Build response with the entry selected
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
|
||||||
mDatabase?.let { database ->
|
mDatabase?.let { database ->
|
||||||
AutofillHelper.buildResponse(this,
|
AutofillHelper.buildResponseAndSetResult(this,
|
||||||
entry.getEntryInfo(database))
|
entry.getEntryInfo(database))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -878,6 +885,8 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
if (mSpecialMode == SpecialMode.DEFAULT) {
|
if (mSpecialMode == SpecialMode.DEFAULT) {
|
||||||
MenuUtil.defaultMenuInflater(inflater, menu)
|
MenuUtil.defaultMenuInflater(inflater, menu)
|
||||||
|
} else {
|
||||||
|
menu.findItem(R.id.menu_reload_database)?.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Menu for recycle bin
|
// Menu for recycle bin
|
||||||
@@ -1003,6 +1012,10 @@ class GroupActivity : LockingActivity(),
|
|||||||
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
|
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.menu_reload_database -> {
|
||||||
|
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
R.id.menu_empty_recycle_bin -> {
|
R.id.menu_empty_recycle_bin -> {
|
||||||
mCurrentGroup?.getChildren()?.let { listChildren ->
|
mCurrentGroup?.getChildren()?.let { listChildren ->
|
||||||
// Automatically delete all elements
|
// Automatically delete all elements
|
||||||
@@ -1310,14 +1323,14 @@ class GroupActivity : LockingActivity(),
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
fun launchForAutofillResult(activity: Activity,
|
fun launchForAutofillResult(activity: Activity,
|
||||||
readOnly: Boolean,
|
readOnly: Boolean,
|
||||||
assistStructure: AssistStructure,
|
autofillComponent: AutofillComponent,
|
||||||
searchInfo: SearchInfo? = null,
|
searchInfo: SearchInfo? = null,
|
||||||
autoSearch: Boolean = false) {
|
autoSearch: Boolean = false) {
|
||||||
checkTimeAndBuildIntent(activity, null, readOnly) { intent ->
|
checkTimeAndBuildIntent(activity, null, readOnly) { intent ->
|
||||||
intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
|
intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
|
||||||
AutofillHelper.startActivityForAutofillResult(activity,
|
AutofillHelper.startActivityForAutofillResult(activity,
|
||||||
intent,
|
intent,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo)
|
searchInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1434,21 +1447,21 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ searchInfo, assistStructure ->
|
{ searchInfo, autofillComponent ->
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
SearchHelper.checkAutoSearchInfo(activity,
|
SearchHelper.checkAutoSearchInfo(activity,
|
||||||
Database.getInstance(),
|
Database.getInstance(),
|
||||||
searchInfo,
|
searchInfo,
|
||||||
{ items ->
|
{ items ->
|
||||||
// Response is build
|
// Response is build
|
||||||
AutofillHelper.buildResponse(activity, items)
|
AutofillHelper.buildResponseAndSetResult(activity, items)
|
||||||
onValidateSpecialMode()
|
onValidateSpecialMode()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Here no search info found, disable auto search
|
// Here no search info found, disable auto search
|
||||||
GroupActivity.launchForAutofillResult(activity,
|
GroupActivity.launchForAutofillResult(activity,
|
||||||
readOnly,
|
readOnly,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo,
|
searchInfo,
|
||||||
false)
|
false)
|
||||||
onLaunchActivitySpecialMode()
|
onLaunchActivitySpecialMode()
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.assist.AssistStructure
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
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.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
|
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
||||||
@@ -720,7 +720,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
|||||||
when (resultCode) {
|
when (resultCode) {
|
||||||
LockingActivity.RESULT_EXIT_LOCK -> {
|
LockingActivity.RESULT_EXIT_LOCK -> {
|
||||||
clearCredentialsViews()
|
clearCredentialsViews()
|
||||||
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
|
Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
|
||||||
}
|
}
|
||||||
Activity.RESULT_CANCELED -> {
|
Activity.RESULT_CANCELED -> {
|
||||||
clearCredentialsViews()
|
clearCredentialsViews()
|
||||||
@@ -838,13 +838,13 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
|||||||
fun launchForAutofillResult(activity: Activity,
|
fun launchForAutofillResult(activity: Activity,
|
||||||
databaseFile: Uri,
|
databaseFile: Uri,
|
||||||
keyFile: Uri?,
|
keyFile: Uri?,
|
||||||
assistStructure: AssistStructure,
|
autofillComponent: AutofillComponent,
|
||||||
searchInfo: SearchInfo?) {
|
searchInfo: SearchInfo?) {
|
||||||
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||||
AutofillHelper.startActivityForAutofillResult(
|
AutofillHelper.startActivityForAutofillResult(
|
||||||
activity,
|
activity,
|
||||||
intent,
|
intent,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo)
|
searchInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -902,11 +902,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
|||||||
searchInfo)
|
searchInfo)
|
||||||
onLaunchActivitySpecialMode()
|
onLaunchActivitySpecialMode()
|
||||||
},
|
},
|
||||||
{ searchInfo, assistStructure -> // Autofill Selection Action
|
{ searchInfo, autofillComponent -> // Autofill Selection Action
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
PasswordActivity.launchForAutofillResult(activity,
|
PasswordActivity.launchForAutofillResult(activity,
|
||||||
databaseUri, keyFile,
|
databaseUri, keyFile,
|
||||||
assistStructure,
|
autofillComponent,
|
||||||
searchInfo)
|
searchInfo)
|
||||||
onLaunchActivitySpecialMode()
|
onLaunchActivitySpecialMode()
|
||||||
} else {
|
} 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
|
package com.kunzisoft.keepass.activities.helpers
|
||||||
|
|
||||||
import android.app.assist.AssistStructure
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.model.RegisterInfo
|
import com.kunzisoft.keepass.model.RegisterInfo
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
@@ -106,7 +106,7 @@ object EntrySelectionHelper {
|
|||||||
|
|
||||||
fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode {
|
fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (AutofillHelper.retrieveAssistStructure(intent) != null)
|
if (AutofillHelper.retrieveAutofillComponent(intent) != null)
|
||||||
return SpecialMode.SELECTION
|
return SpecialMode.SELECTION
|
||||||
}
|
}
|
||||||
return intent.getSerializableExtra(KEY_SPECIAL_MODE) as SpecialMode?
|
return intent.getSerializableExtra(KEY_SPECIAL_MODE) as SpecialMode?
|
||||||
@@ -119,7 +119,7 @@ object EntrySelectionHelper {
|
|||||||
|
|
||||||
fun retrieveTypeModeFromIntent(intent: Intent): TypeMode {
|
fun retrieveTypeModeFromIntent(intent: Intent): TypeMode {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (AutofillHelper.retrieveAssistStructure(intent) != null)
|
if (AutofillHelper.retrieveAutofillComponent(intent) != null)
|
||||||
return TypeMode.AUTOFILL
|
return TypeMode.AUTOFILL
|
||||||
}
|
}
|
||||||
return intent.getSerializableExtra(KEY_TYPE_MODE) as TypeMode? ?: TypeMode.DEFAULT
|
return intent.getSerializableExtra(KEY_TYPE_MODE) as TypeMode? ?: TypeMode.DEFAULT
|
||||||
@@ -136,7 +136,7 @@ object EntrySelectionHelper {
|
|||||||
saveAction: (searchInfo: SearchInfo) -> Unit,
|
saveAction: (searchInfo: SearchInfo) -> Unit,
|
||||||
keyboardSelectionAction: (searchInfo: SearchInfo?) -> Unit,
|
keyboardSelectionAction: (searchInfo: SearchInfo?) -> Unit,
|
||||||
autofillSelectionAction: (searchInfo: SearchInfo?,
|
autofillSelectionAction: (searchInfo: SearchInfo?,
|
||||||
assistStructure: AssistStructure) -> Unit,
|
autofillComponent: AutofillComponent) -> Unit,
|
||||||
autofillRegistrationAction: (registerInfo: RegisterInfo?) -> Unit) {
|
autofillRegistrationAction: (registerInfo: RegisterInfo?) -> Unit) {
|
||||||
|
|
||||||
when (retrieveSpecialModeFromIntent(intent)) {
|
when (retrieveSpecialModeFromIntent(intent)) {
|
||||||
@@ -167,14 +167,14 @@ object EntrySelectionHelper {
|
|||||||
}
|
}
|
||||||
SpecialMode.SELECTION -> {
|
SpecialMode.SELECTION -> {
|
||||||
val searchInfo: SearchInfo? = retrieveSearchInfoFromIntent(intent)
|
val searchInfo: SearchInfo? = retrieveSearchInfoFromIntent(intent)
|
||||||
var assistStructureInit = false
|
var autofillComponentInit = false
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
AutofillHelper.retrieveAssistStructure(intent)?.let { assistStructure ->
|
AutofillHelper.retrieveAutofillComponent(intent)?.let { autofillComponent ->
|
||||||
autofillSelectionAction.invoke(searchInfo, assistStructure)
|
autofillSelectionAction.invoke(searchInfo, autofillComponent)
|
||||||
assistStructureInit = true
|
autofillComponentInit = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!assistStructureInit) {
|
if (!autofillComponentInit) {
|
||||||
if (intent.getSerializableExtra(KEY_SPECIAL_MODE) != null) {
|
if (intent.getSerializableExtra(KEY_SPECIAL_MODE) != null) {
|
||||||
when (retrieveTypeModeFromIntent(intent)) {
|
when (retrieveTypeModeFromIntent(intent)) {
|
||||||
TypeMode.DEFAULT -> {
|
TypeMode.DEFAULT -> {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import com.kunzisoft.keepass.view.SpecialModeView
|
|||||||
abstract class SpecialModeActivity : StylishActivity() {
|
abstract class SpecialModeActivity : StylishActivity() {
|
||||||
|
|
||||||
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
|
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
|
||||||
protected var mTypeMode: TypeMode = TypeMode.DEFAULT
|
private var mTypeMode: TypeMode = TypeMode.DEFAULT
|
||||||
|
|
||||||
private var mSpecialModeView: SpecialModeView? = null
|
private var mSpecialModeView: SpecialModeView? = null
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class App : MultiDexApplication() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onTerminate() {
|
override fun onTerminate() {
|
||||||
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
|
Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
|
||||||
super.onTerminate()
|
super.onTerminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
|
|||||||
UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri),
|
UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri),
|
||||||
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""),
|
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""),
|
||||||
fileDatabaseInfo.exists,
|
fileDatabaseInfo.exists,
|
||||||
fileDatabaseInfo.getModificationString(),
|
fileDatabaseInfo.getLastModificationString(),
|
||||||
fileDatabaseInfo.getSizeString()
|
fileDatabaseInfo.getSizeString()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -90,7 +90,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
|
|||||||
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri),
|
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri),
|
||||||
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
|
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
|
||||||
fileDatabaseInfo.exists,
|
fileDatabaseInfo.exists,
|
||||||
fileDatabaseInfo.getModificationString(),
|
fileDatabaseInfo.getLastModificationString(),
|
||||||
fileDatabaseInfo.getSizeString()
|
fileDatabaseInfo.getSizeString()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -152,7 +152,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
|
|||||||
UriUtil.decode(fileDatabaseHistory.databaseUri),
|
UriUtil.decode(fileDatabaseHistory.databaseUri),
|
||||||
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
|
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
|
||||||
fileDatabaseInfo.exists,
|
fileDatabaseInfo.exists,
|
||||||
fileDatabaseInfo.getModificationString(),
|
fileDatabaseInfo.getLastModificationString(),
|
||||||
fileDatabaseInfo.getSizeString()
|
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
|
package com.kunzisoft.keepass.autofill
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.BlendMode
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.service.autofill.Dataset
|
import android.service.autofill.Dataset
|
||||||
import android.service.autofill.FillResponse
|
import android.service.autofill.FillResponse
|
||||||
|
import android.service.autofill.InlinePresentation
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.autofill.AutofillManager
|
import android.view.autofill.AutofillManager
|
||||||
import android.view.autofill.AutofillValue
|
import android.view.autofill.AutofillValue
|
||||||
|
import android.view.inputmethod.InlineSuggestionsRequest
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.autofill.inline.UiVersions
|
||||||
|
import androidx.autofill.inline.v1.InlineSuggestionUi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
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.Database
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
|
import com.kunzisoft.keepass.icons.createIconFromDatabaseIcon
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||||
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@@ -47,11 +59,17 @@ object AutofillHelper {
|
|||||||
|
|
||||||
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
|
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? {
|
fun retrieveAutofillComponent(intent: Intent?): AutofillComponent? {
|
||||||
intent?.let {
|
intent?.getParcelableExtra<AssistStructure?>(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure ->
|
||||||
return it.getParcelableExtra(ASSIST_STRUCTURE)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
AutofillComponent(assistStructure,
|
||||||
|
intent.getParcelableExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST))
|
||||||
|
} else {
|
||||||
|
AutofillComponent(assistStructure, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -68,26 +86,10 @@ object AutofillHelper {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun addHeader(responseBuilder: FillResponse.Builder,
|
private fun buildDataset(context: Context,
|
||||||
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,
|
|
||||||
entryInfo: EntryInfo,
|
entryInfo: EntryInfo,
|
||||||
struct: StructureParser.Result): Dataset? {
|
struct: StructureParser.Result,
|
||||||
|
inlinePresentation: InlinePresentation?): Dataset? {
|
||||||
val title = makeEntryTitle(entryInfo)
|
val title = makeEntryTitle(entryInfo)
|
||||||
val views = newRemoteViews(context, title, entryInfo.icon)
|
val views = newRemoteViews(context, title, entryInfo.icon)
|
||||||
val builder = Dataset.Builder(views)
|
val builder = Dataset.Builder(views)
|
||||||
@@ -100,6 +102,12 @@ object AutofillHelper {
|
|||||||
builder.setValue(password, AutofillValue.forText(entryInfo.password))
|
builder.setValue(password, AutofillValue.forText(entryInfo.password))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
inlinePresentation?.let {
|
||||||
|
builder.setInlinePresentation(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
builder.build()
|
builder.build()
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
@@ -108,44 +116,120 @@ object AutofillHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
private fun buildInlinePresentationForEntry(context: Context,
|
||||||
|
inlineSuggestionsRequest: InlineSuggestionsRequest,
|
||||||
|
positionItem: Int,
|
||||||
|
entryInfo: EntryInfo): InlinePresentation? {
|
||||||
|
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
|
||||||
|
val maxSuggestion = inlineSuggestionsRequest.maxSuggestionCount
|
||||||
|
|
||||||
|
if (positionItem <= maxSuggestion-1
|
||||||
|
&& inlinePresentationSpecs.size > positionItem) {
|
||||||
|
val inlinePresentationSpec = inlinePresentationSpecs[positionItem]
|
||||||
|
|
||||||
|
// Make sure that the IME spec claims support for v1 UI template.
|
||||||
|
val imeStyle = inlinePresentationSpec.style
|
||||||
|
if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
|
||||||
|
return null
|
||||||
|
|
||||||
|
// Build the content for IME UI
|
||||||
|
val pendingIntent = PendingIntent.getActivity(context,
|
||||||
|
0,
|
||||||
|
Intent(context, AutofillSettingsActivity::class.java),
|
||||||
|
0)
|
||||||
|
return InlinePresentation(
|
||||||
|
InlineSuggestionUi.newContentBuilder(pendingIntent).apply {
|
||||||
|
setContentDescription(context.getString(R.string.autofill_sign_in_prompt))
|
||||||
|
setTitle(entryInfo.title)
|
||||||
|
setSubtitle(entryInfo.username)
|
||||||
|
setStartIcon(Icon.createWithResource(context, R.mipmap.ic_launcher_round).apply {
|
||||||
|
setTintBlendMode(BlendMode.DST)
|
||||||
|
})
|
||||||
|
buildIconFromEntry(context, entryInfo)?.let { icon ->
|
||||||
|
setEndIcon(icon.apply {
|
||||||
|
setTintBlendMode(BlendMode.DST)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.build().slice, inlinePresentationSpec, false)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildResponse(context: Context,
|
||||||
|
entriesInfo: List<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
|
* Build the Autofill response for one entry
|
||||||
*/
|
*/
|
||||||
fun buildResponse(activity: Activity, entryInfo: EntryInfo) {
|
fun buildResponseAndSetResult(activity: Activity, entryInfo: EntryInfo) {
|
||||||
buildResponse(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
|
buildResponseAndSetResult(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the Autofill response for many entry
|
* Build the Autofill response for many entry
|
||||||
*/
|
*/
|
||||||
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) {
|
fun buildResponseAndSetResult(activity: Activity, entriesInfo: List<EntryInfo>) {
|
||||||
if (entriesInfo.isEmpty()) {
|
if (entriesInfo.isEmpty()) {
|
||||||
activity.setResult(Activity.RESULT_CANCELED)
|
activity.setResult(Activity.RESULT_CANCELED)
|
||||||
} else {
|
} else {
|
||||||
var setResultOk = false
|
var setResultOk = false
|
||||||
activity.intent?.extras?.let { extras ->
|
activity.intent?.getParcelableExtra<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { structure ->
|
||||||
if (extras.containsKey(ASSIST_STRUCTURE)) {
|
StructureParser(structure).parse()?.let { result ->
|
||||||
activity.intent?.getParcelableExtra<AssistStructure>(ASSIST_STRUCTURE)?.let { structure ->
|
// New Response
|
||||||
StructureParser(structure).parse()?.let { result ->
|
val response = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
// New Response
|
val inlineSuggestionsRequest = activity.intent?.getParcelableExtra<InlineSuggestionsRequest?>(EXTRA_INLINE_SUGGESTIONS_REQUEST)
|
||||||
val responseBuilder = FillResponse.Builder()
|
if (inlineSuggestionsRequest != null) {
|
||||||
entriesInfo.forEach {
|
Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
buildResponse(activity, entriesInfo, result, inlineSuggestionsRequest)
|
||||||
|
} else {
|
||||||
|
buildResponse(activity, entriesInfo, result, null)
|
||||||
}
|
}
|
||||||
|
val mReplyIntent = Intent()
|
||||||
|
Log.d(activity.javaClass.name, "Successed Autofill auth.")
|
||||||
|
mReplyIntent.putExtra(
|
||||||
|
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
|
||||||
|
response)
|
||||||
|
setResultOk = true
|
||||||
|
activity.setResult(Activity.RESULT_OK, mReplyIntent)
|
||||||
}
|
}
|
||||||
if (!setResultOk) {
|
}
|
||||||
Log.w(activity.javaClass.name, "Failed Autofill auth.")
|
if (!setResultOk) {
|
||||||
activity.setResult(Activity.RESULT_CANCELED)
|
Log.w(activity.javaClass.name, "Failed Autofill auth.")
|
||||||
}
|
activity.setResult(Activity.RESULT_CANCELED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,10 +239,16 @@ object AutofillHelper {
|
|||||||
*/
|
*/
|
||||||
fun startActivityForAutofillResult(activity: Activity,
|
fun startActivityForAutofillResult(activity: Activity,
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
assistStructure: AssistStructure,
|
autofillComponent: AutofillComponent,
|
||||||
searchInfo: SearchInfo?) {
|
searchInfo: SearchInfo?) {
|
||||||
EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.SELECTION)
|
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)
|
EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo)
|
||||||
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
|
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
@@ -192,4 +282,11 @@ object AutofillHelper {
|
|||||||
}
|
}
|
||||||
return presentation
|
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
|
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.Build
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import android.service.autofill.*
|
import android.service.autofill.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.autofill.AutofillId
|
import android.view.autofill.AutofillId
|
||||||
|
import android.view.inputmethod.InlineSuggestionsRequest
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.autofill.inline.UiVersions
|
||||||
|
import androidx.autofill.inline.v1.InlineSuggestionUi
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
|
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||||
import com.kunzisoft.keepass.model.RegisterInfo
|
import com.kunzisoft.keepass.model.RegisterInfo
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
class KeeAutofillService : AutofillService() {
|
class KeeAutofillService : AutofillService() {
|
||||||
|
|
||||||
var applicationIdBlocklist: Set<String>? = null
|
var applicationIdBlocklist: Set<String>? = null
|
||||||
var webDomainBlocklist: Set<String>? = null
|
var webDomainBlocklist: Set<String>? = null
|
||||||
var askToSaveData: Boolean = false
|
var askToSaveData: Boolean = false
|
||||||
|
var autofillInlineSuggestionsEnabled: Boolean = false
|
||||||
private var mLock = AtomicBoolean()
|
private var mLock = AtomicBoolean()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
getPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPreferences() {
|
||||||
applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this)
|
applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this)
|
||||||
webDomainBlocklist = PreferencesUtil.webDomainBlocklist(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,
|
override fun onFillRequest(request: FillRequest,
|
||||||
@@ -75,7 +88,16 @@ class KeeAutofillService : AutofillService() {
|
|||||||
}
|
}
|
||||||
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain ->
|
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain ->
|
||||||
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,
|
private fun launchSelection(searchInfo: SearchInfo,
|
||||||
parseResult: StructureParser.Result,
|
parseResult: StructureParser.Result,
|
||||||
|
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
||||||
callback: FillCallback) {
|
callback: FillCallback) {
|
||||||
SearchHelper.checkAutoSearchInfo(this,
|
SearchHelper.checkAutoSearchInfo(this,
|
||||||
Database.getInstance(),
|
Database.getInstance(),
|
||||||
searchInfo,
|
searchInfo,
|
||||||
{ items ->
|
{ items ->
|
||||||
val responseBuilder = FillResponse.Builder()
|
callback.onSuccess(
|
||||||
AutofillHelper.addHeader(responseBuilder, packageName,
|
AutofillHelper.buildResponse(this,
|
||||||
parseResult.webDomain, parseResult.applicationId)
|
items, parseResult, inlineSuggestionsRequest)
|
||||||
items.forEach {
|
)
|
||||||
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
|
|
||||||
}
|
|
||||||
callback.onSuccess(responseBuilder.build())
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Show UI if no search result
|
// Show UI if no search result
|
||||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
showUIForEntrySelection(parseResult,
|
||||||
|
searchInfo, inlineSuggestionsRequest, callback)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Show UI if database not open
|
// Show UI if database not open
|
||||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
showUIForEntrySelection(parseResult,
|
||||||
|
searchInfo, inlineSuggestionsRequest, callback)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
|
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
|
||||||
searchInfo: SearchInfo,
|
searchInfo: SearchInfo,
|
||||||
|
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
||||||
callback: FillCallback) {
|
callback: FillCallback) {
|
||||||
parseResult.allAutofillIds().let { autofillIds ->
|
parseResult.allAutofillIds().let { autofillIds ->
|
||||||
if (autofillIds.isNotEmpty()) {
|
if (autofillIds.isNotEmpty()) {
|
||||||
// If the entire Autofill Response is authenticated, AuthActivity is used
|
// If the entire Autofill Response is authenticated, AuthActivity is used
|
||||||
// to generate Response.
|
// to generate Response.
|
||||||
val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(this,
|
val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(this,
|
||||||
searchInfo)
|
searchInfo, inlineSuggestionsRequest)
|
||||||
val responseBuilder = FillResponse.Builder()
|
val responseBuilder = FillResponse.Builder()
|
||||||
val remoteViewsUnlock: RemoteViews = if (!parseResult.webDomain.isNullOrEmpty()) {
|
val remoteViewsUnlock: RemoteViews = if (!parseResult.webDomain.isNullOrEmpty()) {
|
||||||
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
|
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
|
||||||
@@ -149,7 +172,40 @@ class KeeAutofillService : AutofillService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Build response
|
|
||||||
|
// Build inline presentation
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||||
|
&& autofillInlineSuggestionsEnabled) {
|
||||||
|
var inlinePresentation: InlinePresentation? = null
|
||||||
|
inlineSuggestionsRequest?.let {
|
||||||
|
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
|
||||||
|
if (inlineSuggestionsRequest.maxSuggestionCount > 0
|
||||||
|
&& inlinePresentationSpecs.size > 0) {
|
||||||
|
val inlinePresentationSpec = inlinePresentationSpecs[0]
|
||||||
|
|
||||||
|
// Make sure that the IME spec claims support for v1 UI template.
|
||||||
|
val imeStyle = inlinePresentationSpec.style
|
||||||
|
if (UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1)) {
|
||||||
|
// Build the content for IME UI
|
||||||
|
inlinePresentation = InlinePresentation(
|
||||||
|
InlineSuggestionUi.newContentBuilder(
|
||||||
|
PendingIntent.getActivity(this,
|
||||||
|
0,
|
||||||
|
Intent(this, AutofillSettingsActivity::class.java),
|
||||||
|
0)
|
||||||
|
).apply {
|
||||||
|
setContentDescription(getString(R.string.autofill_sign_in_prompt))
|
||||||
|
setTitle(getString(R.string.autofill_sign_in_prompt))
|
||||||
|
setStartIcon(Icon.createWithResource(this@KeeAutofillService, R.mipmap.ic_launcher_round).apply {
|
||||||
|
setTintBlendMode(BlendMode.DST)
|
||||||
|
})
|
||||||
|
}.build().slice, inlinePresentationSpec, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Build response
|
||||||
|
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation)
|
||||||
|
}
|
||||||
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
|
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
|
||||||
callback.onSuccess(responseBuilder.build())
|
callback.onSuccess(responseBuilder.build())
|
||||||
}
|
}
|
||||||
@@ -190,6 +246,7 @@ class KeeAutofillService : AutofillService() {
|
|||||||
|
|
||||||
override fun onConnected() {
|
override fun onConnected() {
|
||||||
Log.d(TAG, "onConnected")
|
Log.d(TAG, "onConnected")
|
||||||
|
getPreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisconnected() {
|
override fun onDisconnected() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import java.util.*
|
|||||||
* Parse AssistStructure and guess username and password fields.
|
* Parse AssistStructure and guess username and password fields.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@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 result: Result? = null
|
||||||
|
|
||||||
private var usernameNeeded = true
|
private var usernameNeeded = true
|
||||||
@@ -274,7 +274,7 @@ internal class StructureParser(private val structure: AssistStructure) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
internal class Result {
|
class Result {
|
||||||
var applicationId: String? = null
|
var applicationId: String? = null
|
||||||
|
|
||||||
var webDomain: String? = null
|
var webDomain: String? = null
|
||||||
|
|||||||
@@ -554,18 +554,20 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
readOnlyEducationPerformed: Boolean,
|
readOnlyEducationPerformed: Boolean,
|
||||||
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
|
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
|
||||||
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
|
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
try {
|
||||||
&& !readOnlyEducationPerformed) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
|
&& !readOnlyEducationPerformed) {
|
||||||
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
|
||||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||||
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
|
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||||
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
|
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
|
||||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
|
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
|
||||||
onEducationViewClick,
|
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
|
||||||
onOuterViewClick)
|
onEducationViewClick,
|
||||||
}
|
onOuterViewClick)
|
||||||
|
}
|
||||||
|
} catch (ignored: Exception) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
|
|||||||
@@ -29,11 +29,12 @@ object StreamCipherFactory {
|
|||||||
|
|
||||||
private val SALSA_IV = byteArrayOf(0xE8.toByte(), 0x30, 0x09, 0x4B, 0x97.toByte(), 0x20, 0x5D, 0x2A)
|
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 {
|
return when {
|
||||||
alg === CrsAlgorithm.Salsa20 -> getSalsa20(key)
|
alg === CrsAlgorithm.Salsa20 -> getSalsa20(key)
|
||||||
alg === CrsAlgorithm.ChaCha20 -> getChaCha20(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.database.element.Database
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.utils.closeDatabase
|
|
||||||
|
|
||||||
class CreateDatabaseRunnable(context: Context,
|
class CreateDatabaseRunnable(context: Context,
|
||||||
private val mDatabase: Database,
|
private val mDatabase: Database,
|
||||||
@@ -47,7 +46,7 @@ class CreateDatabaseRunnable(context: Context,
|
|||||||
createData(mDatabaseUri, databaseName, rootName)
|
createData(mDatabaseUri, databaseName, rootName)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
|
mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
|
||||||
setError(e)
|
setError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
|||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.utils.closeDatabase
|
|
||||||
|
|
||||||
class LoadDatabaseRunnable(private val context: Context,
|
class LoadDatabaseRunnable(private val context: Context,
|
||||||
private val mDatabase: Database,
|
private val mDatabase: Database,
|
||||||
@@ -47,7 +46,7 @@ class LoadDatabaseRunnable(private val context: Context,
|
|||||||
|
|
||||||
override fun onStartRun() {
|
override fun onStartRun() {
|
||||||
// Clear before we load
|
// Clear before we load
|
||||||
mDatabase.closeAndClear(UriUtil.getBinaryDir(context))
|
mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionRun() {
|
override fun onActionRun() {
|
||||||
@@ -59,9 +58,6 @@ class LoadDatabaseRunnable(private val context: Context,
|
|||||||
mFixDuplicateUUID,
|
mFixDuplicateUUID,
|
||||||
progressTaskUpdater)
|
progressTaskUpdater)
|
||||||
}
|
}
|
||||||
catch (e: DuplicateUuidDatabaseException) {
|
|
||||||
setError(e)
|
|
||||||
}
|
|
||||||
catch (e: LoadDatabaseException) {
|
catch (e: LoadDatabaseException) {
|
||||||
setError(e)
|
setError(e)
|
||||||
}
|
}
|
||||||
@@ -83,7 +79,7 @@ class LoadDatabaseRunnable(private val context: Context,
|
|||||||
// Register the current time to init the lock timer
|
// Register the current time to init the lock timer
|
||||||
PreferencesUtil.saveCurrentTime(context)
|
PreferencesUtil.saveCurrentTime(context)
|
||||||
} else {
|
} 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.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.fragment.app.FragmentActivity
|
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.app.database.CipherDatabaseEntity
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
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.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
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
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_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_ENTRY_HISTORY
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
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_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_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_REMOVE_UNLINKED_DATA_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||||
@@ -84,6 +88,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
|||||||
private var serviceConnection: ServiceConnection? = null
|
private var serviceConnection: ServiceConnection? = null
|
||||||
|
|
||||||
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
|
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
|
||||||
|
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
|
||||||
|
|
||||||
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
||||||
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
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,
|
private fun startDialog(titleId: Int? = null,
|
||||||
messageId: Int? = null,
|
messageId: Int? = null,
|
||||||
warningId: Int? = null) {
|
warningId: Int? = null) {
|
||||||
@@ -140,11 +167,14 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
|||||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||||
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
|
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
|
||||||
addActionTaskListener(actionTaskListener)
|
addActionTaskListener(actionTaskListener)
|
||||||
|
addDatabaseFileInfoListener(databaseInfoListener)
|
||||||
getService().checkAction()
|
getService().checkAction()
|
||||||
|
getService().checkDatabaseInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
|
||||||
mBinder?.removeActionTaskListener(actionTaskListener)
|
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||||
mBinder = null
|
mBinder = null
|
||||||
}
|
}
|
||||||
@@ -206,6 +236,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
|||||||
fun unregisterProgressTask() {
|
fun unregisterProgressTask() {
|
||||||
stopDialog()
|
stopDialog()
|
||||||
|
|
||||||
|
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
|
||||||
mBinder?.removeActionTaskListener(actionTaskListener)
|
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||||
mBinder = null
|
mBinder = null
|
||||||
|
|
||||||
@@ -264,6 +295,13 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
|||||||
, ACTION_DATABASE_LOAD_TASK)
|
, 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,
|
fun startDatabaseAssignPassword(databaseUri: Uri,
|
||||||
masterPasswordChecked: Boolean,
|
masterPasswordChecked: Boolean,
|
||||||
masterPassword: String?,
|
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.NodeIdInt
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
import com.kunzisoft.keepass.database.exception.*
|
||||||
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.file.DatabaseHeaderKDB
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||||
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
|
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
|
||||||
@@ -330,29 +327,11 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(LoadDatabaseException::class)
|
@Throws(LoadDatabaseException::class)
|
||||||
fun loadData(uri: Uri, password: String?, keyfile: Uri?,
|
private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
|
||||||
readOnly: Boolean,
|
openDatabaseKDB: (InputStream) -> DatabaseKDB,
|
||||||
contentResolver: ContentResolver,
|
openDatabaseKDBX: (InputStream) -> DatabaseKDBX) {
|
||||||
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
|
|
||||||
var databaseInputStream: InputStream? = null
|
var databaseInputStream: InputStream? = null
|
||||||
var keyFileInputStream: InputStream? = null
|
|
||||||
try {
|
try {
|
||||||
// Get keyFile inputStream
|
|
||||||
keyfile?.let {
|
|
||||||
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load Data, pass Uris as InputStreams
|
// Load Data, pass Uris as InputStreams
|
||||||
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
|
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
|
||||||
?: throw IOException("Database input stream cannot be retrieve")
|
?: throw IOException("Database input stream cannot be retrieve")
|
||||||
@@ -374,22 +353,10 @@ class Database {
|
|||||||
|
|
||||||
when {
|
when {
|
||||||
// Header of database KDB
|
// Header of database KDB
|
||||||
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB(
|
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(openDatabaseKDB(databaseInputStream))
|
||||||
cacheDirectory,
|
|
||||||
fixDuplicateUUID)
|
|
||||||
.openDatabase(databaseInputStream,
|
|
||||||
password,
|
|
||||||
keyFileInputStream,
|
|
||||||
progressTaskUpdater))
|
|
||||||
|
|
||||||
// Header of database KDBX
|
// Header of database KDBX
|
||||||
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX(
|
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(openDatabaseKDBX(databaseInputStream))
|
||||||
cacheDirectory,
|
|
||||||
fixDuplicateUUID)
|
|
||||||
.openDatabase(databaseInputStream,
|
|
||||||
password,
|
|
||||||
keyFileInputStream,
|
|
||||||
progressTaskUpdater))
|
|
||||||
|
|
||||||
// Header not recognized
|
// Header not recognized
|
||||||
else -> throw SignatureDatabaseException()
|
else -> throw SignatureDatabaseException()
|
||||||
@@ -397,14 +364,90 @@ class Database {
|
|||||||
|
|
||||||
this.mSearchHelper = SearchHelper()
|
this.mSearchHelper = SearchHelper()
|
||||||
loaded = true
|
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) {
|
} catch (e: LoadDatabaseException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw FileNotFoundDatabaseException()
|
throw LoadDatabaseException(e)
|
||||||
} finally {
|
} finally {
|
||||||
keyFileInputStream?.close()
|
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? {
|
max: Int = Integer.MAX_VALUE): Group? {
|
||||||
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
|
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
|
||||||
searchInfoString, SearchParameters().apply {
|
searchInfoString, SearchParameters().apply {
|
||||||
searchInTitles = false
|
searchInTitles = true
|
||||||
searchInUserNames = false
|
searchInUserNames = false
|
||||||
searchInPasswords = false
|
searchInPasswords = false
|
||||||
searchInUrls = true
|
searchInUrls = true
|
||||||
@@ -531,7 +574,7 @@ class Database {
|
|||||||
this.fileUri = uri
|
this.fileUri = uri
|
||||||
}
|
}
|
||||||
|
|
||||||
fun closeAndClear(filesDirectory: File? = null) {
|
fun clear(filesDirectory: File? = null) {
|
||||||
drawFactory.clearCache()
|
drawFactory.clearCache()
|
||||||
// Delete the cache of the database if present
|
// Delete the cache of the database if present
|
||||||
mDatabaseKDB?.clearCache()
|
mDatabaseKDB?.clearCache()
|
||||||
@@ -544,7 +587,10 @@ class Database {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Unable to clear the directory cache.", e)
|
Log.e(TAG, "Unable to clear the directory cache.", e)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAndClose(filesDirectory: File? = null) {
|
||||||
|
clear(filesDirectory)
|
||||||
this.mDatabaseKDB = null
|
this.mDatabaseKDB = null
|
||||||
this.mDatabaseKDBX = null
|
this.mDatabaseKDBX = null
|
||||||
this.fileUri = null
|
this.fileUri = null
|
||||||
|
|||||||
@@ -426,6 +426,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
entryInfo.icon = icon
|
entryInfo.icon = icon
|
||||||
entryInfo.username = username
|
entryInfo.username = username
|
||||||
entryInfo.password = password
|
entryInfo.password = password
|
||||||
|
entryInfo.creationTime = creationTime
|
||||||
|
entryInfo.modificationTime = lastModificationTime
|
||||||
entryInfo.expires = expires
|
entryInfo.expires = expires
|
||||||
entryInfo.expiryTime = expiryTime
|
entryInfo.expiryTime = expiryTime
|
||||||
entryInfo.url = url
|
entryInfo.url = url
|
||||||
@@ -456,6 +458,9 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
icon = newEntryInfo.icon
|
icon = newEntryInfo.icon
|
||||||
username = newEntryInfo.username
|
username = newEntryInfo.username
|
||||||
password = newEntryInfo.password
|
password = newEntryInfo.password
|
||||||
|
// Update date time, creation time stay as is
|
||||||
|
lastModificationTime = DateInstant()
|
||||||
|
lastAccessTime = DateInstant()
|
||||||
expires = newEntryInfo.expires
|
expires = newEntryInfo.expires
|
||||||
expiryTime = newEntryInfo.expiryTime
|
expiryTime = newEntryInfo.expiryTime
|
||||||
url = newEntryInfo.url
|
url = newEntryInfo.url
|
||||||
@@ -464,9 +469,6 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
database?.binaryPool?.let { binaryPool ->
|
database?.binaryPool?.let { binaryPool ->
|
||||||
addAttachments(binaryPool, newEntryInfo.attachments)
|
addAttachments(binaryPool, newEntryInfo.attachments)
|
||||||
}
|
}
|
||||||
// Update date time
|
|
||||||
lastAccessTime = DateInstant()
|
|
||||||
lastModificationTime = DateInstant()
|
|
||||||
|
|
||||||
database?.stopManageEntry(this)
|
database?.stopManageEntry(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,10 +163,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
finalKey = messageDigest.digest()
|
finalKey = messageDigest.digest()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createGroup(): GroupKDB {
|
override fun createGroup(): GroupKDB {
|
||||||
return 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.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_3
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||||
|
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
|
||||||
|
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
||||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||||
|
import org.apache.commons.codec.binary.Hex
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
import org.w3c.dom.Text
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@@ -180,7 +182,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
when (oldCompression) {
|
when (oldCompression) {
|
||||||
CompressionAlgorithm.None -> {
|
CompressionAlgorithm.None -> {
|
||||||
when (newCompression) {
|
when (newCompression) {
|
||||||
CompressionAlgorithm.None -> {}
|
CompressionAlgorithm.None -> {
|
||||||
|
}
|
||||||
CompressionAlgorithm.GZip -> {
|
CompressionAlgorithm.GZip -> {
|
||||||
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
|
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
|
||||||
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
|
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
|
||||||
@@ -198,7 +201,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
CompressionAlgorithm.None -> {
|
CompressionAlgorithm.None -> {
|
||||||
decompressAllBinaries()
|
decompressAllBinaries()
|
||||||
}
|
}
|
||||||
CompressionAlgorithm.GZip -> {}
|
CompressionAlgorithm.GZip -> {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,36 +382,82 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
try {
|
try {
|
||||||
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
|
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
|
||||||
} catch (e : ParserConfigurationException) {
|
} 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 documentBuilder = documentBuilderFactory.newDocumentBuilder()
|
||||||
val doc = documentBuilder.parse(keyInputStream)
|
val doc = documentBuilder.parse(keyInputStream)
|
||||||
|
|
||||||
|
var xmlKeyFileVersion = 1F
|
||||||
|
|
||||||
val docElement = doc.documentElement
|
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
|
return null
|
||||||
}
|
}
|
||||||
|
if (keyFileChildNodes.length < 2)
|
||||||
val children = docElement.childNodes
|
|
||||||
if (children.length < 2) {
|
|
||||||
return null
|
return null
|
||||||
}
|
for (keyFileChildPosition in 0 until keyFileChildNodes.length) {
|
||||||
|
val keyFileChildNode = keyFileChildNodes.item(keyFileChildPosition)
|
||||||
for (i in 0 until children.length) {
|
// <Meta>
|
||||||
val child = children.item(i)
|
if (keyFileChildNode.nodeName.equals(XML_NODE_META_NAME, ignoreCase = true)) {
|
||||||
|
val metaChildNodes = keyFileChildNode.childNodes
|
||||||
if (child.nodeName.equals(KeyElementName, ignoreCase = true)) {
|
for (metaChildPosition in 0 until metaChildNodes.length) {
|
||||||
val keyChildren = child.childNodes
|
val metaChildNode = metaChildNodes.item(metaChildPosition)
|
||||||
for (j in 0 until keyChildren.length) {
|
// <Version>
|
||||||
val keyChild = keyChildren.item(j)
|
if (metaChildNode.nodeName.equals(XML_NODE_VERSION_NAME, ignoreCase = true)) {
|
||||||
if (keyChild.nodeName.equals(KeyDataElementName, ignoreCase = true)) {
|
val versionChildNodes = metaChildNode.childNodes
|
||||||
val children2 = keyChild.childNodes
|
for (versionChildPosition in 0 until versionChildNodes.length) {
|
||||||
for (k in 0 until children2.length) {
|
val versionChildNode = versionChildNodes.item(versionChildPosition)
|
||||||
val text = children2.item(k)
|
if (versionChildNode.nodeType == Node.TEXT_NODE) {
|
||||||
if (text.nodeType == Node.TEXT_NODE) {
|
val versionText = versionChildNode.textContent.removeSpaceChars()
|
||||||
val txt = text as Text
|
try {
|
||||||
return Base64.decode(txt.nodeValue, BASE_64_FLAG)
|
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
|
||||||
|
return Base64.decode(dataString, BASE_64_FLAG)
|
||||||
|
}
|
||||||
|
2F -> {
|
||||||
|
return if (hashString != null
|
||||||
|
&& checkKeyFileHash(dataString, hashString)) {
|
||||||
|
Log.i(TAG, "Successful key file hash check.")
|
||||||
|
Hex.decodeHex(dataString)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unable to check the hash of the key file.")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,10 +467,26 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkKeyFileHash(data: String, hash: String): Boolean {
|
||||||
|
val digest: MessageDigest?
|
||||||
|
var success = false
|
||||||
|
try {
|
||||||
|
digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
digest?.reset()
|
||||||
|
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
|
||||||
|
val dataDigest = digest.digest(Hex.decodeHex(data))
|
||||||
|
.copyOfRange(0, 4)
|
||||||
|
.toHexString()
|
||||||
|
success = dataDigest == hash
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
override fun newGroupId(): NodeIdUUID {
|
override fun newGroupId(): NodeIdUUID {
|
||||||
var newId: NodeIdUUID
|
var newId: NodeIdUUID
|
||||||
do {
|
do {
|
||||||
@@ -634,11 +700,12 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
|
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 DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited
|
||||||
|
|
||||||
private const val RootElementName = "KeyFile"
|
private const val XML_NODE_ROOT_NAME = "KeyFile"
|
||||||
//private const val MetaElementName = "Meta";
|
private const val XML_NODE_META_NAME = "Meta"
|
||||||
//private const val VersionElementName = "Version";
|
private const val XML_NODE_VERSION_NAME = "Version"
|
||||||
private const val KeyElementName = "Key"
|
private const val XML_NODE_KEY_NAME = "Key"
|
||||||
private const val KeyDataElementName = "Data"
|
private const val XML_NODE_DATA_NAME = "Data"
|
||||||
|
private const val XML_ATTRIBUTE_DATA_HASH = "Hash"
|
||||||
|
|
||||||
const val BASE_64_FLAG = Base64.NO_WRAP
|
const val BASE_64_FLAG = Base64.NO_WRAP
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
|||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||||
import java.io.*
|
import org.apache.commons.codec.binary.Hex
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.UnsupportedEncodingException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -124,42 +128,35 @@ abstract class DatabaseVersioned<
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
|
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
|
||||||
|
|
||||||
val keyByteArrayOutputStream = ByteArrayOutputStream()
|
val keyData = keyInputStream.readBytes()
|
||||||
keyInputStream.copyTo(keyByteArrayOutputStream)
|
|
||||||
val keyData = keyByteArrayOutputStream.toByteArray()
|
|
||||||
|
|
||||||
val keyByteArrayInputStream = ByteArrayInputStream(keyData)
|
// Check XML key file
|
||||||
val key = loadXmlKeyFile(keyByteArrayInputStream)
|
val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
|
||||||
if (key != null) {
|
if (xmlKeyByteArray != null) {
|
||||||
return key
|
return xmlKeyByteArray
|
||||||
}
|
}
|
||||||
|
|
||||||
when (keyData.size.toLong()) {
|
// Check 32 bytes key file
|
||||||
32L -> return keyData
|
when (keyData.size) {
|
||||||
64L -> try {
|
32 -> return keyData
|
||||||
return hexStringToByteArray(String(keyData))
|
64 -> try {
|
||||||
} catch (e: IndexOutOfBoundsException) {
|
return Hex.decodeHex(String(keyData))
|
||||||
|
} catch (ignoredException: Exception) {
|
||||||
// Key is not base 64, treat it as binary data
|
// Key is not base 64, treat it as binary data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val messageDigest: MessageDigest
|
// Hash file as binary data
|
||||||
try {
|
try {
|
||||||
messageDigest = MessageDigest.getInstance("SHA-256")
|
return MessageDigest.getInstance("SHA-256").digest(keyData)
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
throw IOException("SHA-256 not supported")
|
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 {
|
open fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
|
||||||
if (password == null && !containsKeyFile)
|
if (password == null && !containsKeyFile)
|
||||||
@@ -391,16 +388,5 @@ abstract class DatabaseVersioned<
|
|||||||
private const val TAG = "DatabaseVersioned"
|
private const val TAG = "DatabaseVersioned"
|
||||||
|
|
||||||
val UUID_ZERO = UUID(0, 0)
|
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 {
|
open class LoadDatabaseException : DatabaseException {
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
override var errorId: Int = R.string.error_load_database
|
override var errorId: Int = R.string.error_load_database
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
constructor(throwable: Throwable) : super(throwable)
|
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 {
|
class FileNotFoundDatabaseException : LoadDatabaseException {
|
||||||
@StringRes
|
@StringRes
|
||||||
override var errorId: Int = R.string.file_not_found_content
|
override var errorId: Int = R.string.file_not_found_content
|
||||||
@@ -67,7 +59,6 @@ class FileNotFoundDatabaseException : LoadDatabaseException {
|
|||||||
class InvalidAlgorithmDatabaseException : LoadDatabaseException {
|
class InvalidAlgorithmDatabaseException : LoadDatabaseException {
|
||||||
@StringRes
|
@StringRes
|
||||||
override var errorId: Int = R.string.invalid_algorithm
|
override var errorId: Int = R.string.invalid_algorithm
|
||||||
|
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
constructor(exception: Throwable) : super(exception)
|
constructor(exception: Throwable) : super(exception)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
|
|||||||
abstract fun openDatabase(databaseInputStream: InputStream,
|
abstract fun openDatabase(databaseInputStream: InputStream,
|
||||||
password: String?,
|
password: String?,
|
||||||
keyInputStream: InputStream?,
|
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.
|
* Load a KDB database file.
|
||||||
*/
|
*/
|
||||||
class DatabaseInputKDB(cacheDirectory: File,
|
class DatabaseInputKDB(cacheDirectory: File)
|
||||||
private val fixDuplicateUUID: Boolean = false)
|
|
||||||
: DatabaseInput<DatabaseKDB>(cacheDirectory) {
|
: DatabaseInput<DatabaseKDB>(cacheDirectory) {
|
||||||
|
|
||||||
private lateinit var mDatabaseToOpen: DatabaseKDB
|
private lateinit var mDatabaseToOpen: DatabaseKDB
|
||||||
@@ -55,7 +54,28 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
override fun openDatabase(databaseInputStream: InputStream,
|
override fun openDatabase(databaseInputStream: InputStream,
|
||||||
password: String?,
|
password: String?,
|
||||||
keyInputStream: InputStream?,
|
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 {
|
try {
|
||||||
// Load entire file, most of it's encrypted.
|
// Load entire file, most of it's encrypted.
|
||||||
@@ -84,7 +104,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
mDatabaseToOpen = DatabaseKDB()
|
mDatabaseToOpen = DatabaseKDB()
|
||||||
|
|
||||||
mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID
|
mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID
|
||||||
mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
|
assignMasterKey?.invoke()
|
||||||
|
|
||||||
// Select algorithm
|
// Select algorithm
|
||||||
when {
|
when {
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ import com.kunzisoft.keepass.R
|
|||||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
import com.kunzisoft.keepass.crypto.StreamCipherFactory
|
import com.kunzisoft.keepass.crypto.StreamCipherFactory
|
||||||
import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
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.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
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.CompressionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
|
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.icon.IconImageCustom
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
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.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.database.exception.*
|
import com.kunzisoft.keepass.database.exception.*
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||||
@@ -63,8 +63,7 @@ import javax.crypto.Cipher
|
|||||||
import javax.crypto.CipherInputStream
|
import javax.crypto.CipherInputStream
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class DatabaseInputKDBX(cacheDirectory: File,
|
class DatabaseInputKDBX(cacheDirectory: File)
|
||||||
private val fixDuplicateUUID: Boolean = false)
|
|
||||||
: DatabaseInput<DatabaseKDBX>(cacheDirectory) {
|
: DatabaseInput<DatabaseKDBX>(cacheDirectory) {
|
||||||
|
|
||||||
private var randomStream: StreamCipher? = null
|
private var randomStream: StreamCipher? = null
|
||||||
@@ -98,12 +97,30 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
override fun openDatabase(databaseInputStream: InputStream,
|
override fun openDatabase(databaseInputStream: InputStream,
|
||||||
password: String?,
|
password: String?,
|
||||||
keyInputStream: InputStream?,
|
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 {
|
try {
|
||||||
// TODO performance
|
|
||||||
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
|
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
|
||||||
|
|
||||||
mDatabase = DatabaseKDBX()
|
mDatabase = DatabaseKDBX()
|
||||||
|
|
||||||
mDatabase.changeDuplicateId = fixDuplicateUUID
|
mDatabase.changeDuplicateId = fixDuplicateUUID
|
||||||
@@ -116,9 +133,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
hashOfHeader = headerAndHash.hash
|
hashOfHeader = headerAndHash.hash
|
||||||
val pbHeader = headerAndHash.header
|
val pbHeader = headerAndHash.header
|
||||||
|
|
||||||
mDatabase.retrieveMasterKey(password, keyInputStream)
|
assignMasterKey?.invoke()
|
||||||
mDatabase.makeFinalKey(header.masterSeed)
|
mDatabase.makeFinalKey(header.masterSeed)
|
||||||
// TODO performance
|
|
||||||
|
|
||||||
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
|
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
|
||||||
val engine: CipherEngine
|
val engine: CipherEngine
|
||||||
@@ -185,10 +201,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
loadInnerHeader(inputStreamXml, header)
|
loadInnerHeader(inputStreamXml, header)
|
||||||
}
|
}
|
||||||
|
|
||||||
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
|
try {
|
||||||
|
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
|
||||||
if (randomStream == null) {
|
} catch (e: Exception) {
|
||||||
throw ArcFourDatabaseException()
|
throw LoadDatabaseException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
readDocumentStreamed(createPullParser(inputStreamXml))
|
readDocumentStreamed(createPullParser(inputStreamXml))
|
||||||
@@ -436,8 +452,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
val strData = readString(xpp)
|
val strData = readString(xpp)
|
||||||
if (strData.isNotEmpty()) {
|
if (strData.isNotEmpty()) {
|
||||||
customIconData = Base64.decode(strData, BASE_64_FLAG)
|
customIconData = Base64.decode(strData, BASE_64_FLAG)
|
||||||
} else {
|
|
||||||
assert(false)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
readUnknown(xpp)
|
readUnknown(xpp)
|
||||||
@@ -958,7 +972,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
// Create empty binary if not retrieved in pool
|
// Create empty binary if not retrieved in pool
|
||||||
if (binaryRetrieve == null) {
|
if (binaryRetrieve == null) {
|
||||||
binaryRetrieve = mDatabase.buildNewBinary(cacheDirectory,
|
binaryRetrieve = mDatabase.buildNewBinary(cacheDirectory,
|
||||||
compression = false, protection = true, binaryPoolId = id)
|
compression = false, protection = false, binaryPoolId = id)
|
||||||
}
|
}
|
||||||
return binaryRetrieve
|
return binaryRetrieve
|
||||||
}
|
}
|
||||||
@@ -1024,29 +1038,20 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
return xpp.safeNextText()
|
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)
|
@Throws(XmlPullParserException::class, IOException::class)
|
||||||
private fun readProtectedBase64String(xpp: XmlPullParser): ByteArray? {
|
private fun readProtectedBase64String(xpp: XmlPullParser): ByteArray? {
|
||||||
//(xpp.getEventType() == XmlPullParser.START_TAG);
|
|
||||||
|
|
||||||
if (xpp.attributeCount > 0) {
|
if (xpp.attributeCount > 0) {
|
||||||
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
|
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
|
||||||
if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) {
|
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
|
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)
|
class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class)
|
||||||
constructor(private val databaseKDBX: DatabaseKDBX,
|
constructor(private val databaseKDBX: DatabaseKDBX,
|
||||||
private val header: DatabaseHeaderKDBX,
|
private val header: DatabaseHeaderKDBX,
|
||||||
outputStream: OutputStream) : DatabaseHeaderOutput() {
|
outputStream: OutputStream) {
|
||||||
|
|
||||||
private val los: LittleEndianDataOutputStream
|
private val los: LittleEndianDataOutputStream
|
||||||
private val mos: MacOutputStream
|
private val mos: MacOutputStream
|
||||||
private val dos: DigestOutputStream
|
private val dos: DigestOutputStream
|
||||||
lateinit var headerHmac: ByteArray
|
lateinit var headerHmac: ByteArray
|
||||||
|
|
||||||
|
var hashOfHeader: ByteArray? = null
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
||||||
val md: MessageDigest
|
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.NoSuchAlgorithmException
|
||||||
import java.security.SecureRandom
|
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)
|
@Throws(DatabaseOutputException::class)
|
||||||
protected open fun setIVs(header: Header): SecureRandom {
|
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
|
// and remove any orphaned nodes that are no longer part of the tree hierarchy
|
||||||
sortGroupsForOutput()
|
sortGroupsForOutput()
|
||||||
|
|
||||||
val header = outputHeader(mOS)
|
val header = outputHeader(mOutputStream)
|
||||||
|
|
||||||
val finalKey = getFinalKey(header)
|
val finalKey = getFinalKey(header)
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
|||||||
cipher.init(Cipher.ENCRYPT_MODE,
|
cipher.init(Cipher.ENCRYPT_MODE,
|
||||||
SecretKeySpec(finalKey, "AES"),
|
SecretKeySpec(finalKey, "AES"),
|
||||||
IvParameterSpec(header.encryptionIV))
|
IvParameterSpec(header.encryptionIV))
|
||||||
val cos = CipherOutputStream(mOS, cipher)
|
val cos = CipherOutputStream(mOutputStream, cipher)
|
||||||
val bos = BufferedOutputStream(cos)
|
val bos = BufferedOutputStream(cos)
|
||||||
outputPlanGroupAndEntries(bos)
|
outputPlanGroupAndEntries(bos)
|
||||||
bos.flush()
|
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.group.GroupKDBX
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
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.MemoryProtectionConfig
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
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.DatabaseKDBXXML
|
||||||
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import org.bouncycastle.crypto.StreamCipher
|
import org.bouncycastle.crypto.StreamCipher
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import org.xmlpull.v1.XmlSerializer
|
import org.xmlpull.v1.XmlSerializer
|
||||||
@@ -58,6 +58,7 @@ import java.util.*
|
|||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.CipherOutputStream
|
import javax.crypto.CipherOutputStream
|
||||||
|
import kotlin.experimental.or
|
||||||
|
|
||||||
|
|
||||||
class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||||
@@ -81,20 +82,19 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
throw DatabaseOutputException("No such cipher", e)
|
throw DatabaseOutputException("No such cipher", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
header = outputHeader(mOS)
|
header = outputHeader(mOutputStream)
|
||||||
|
|
||||||
val osPlain: OutputStream
|
val osPlain: OutputStream
|
||||||
osPlain = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
|
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)
|
cos.write(header!!.streamStartBytes)
|
||||||
|
|
||||||
HashedBlockOutputStream(cos)
|
HashedBlockOutputStream(cos)
|
||||||
} else {
|
} else {
|
||||||
mOS.write(hashOfHeader!!)
|
mOutputStream.write(hashOfHeader!!)
|
||||||
mOS.write(headerHmac!!)
|
mOutputStream.write(headerHmac!!)
|
||||||
|
|
||||||
|
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!))
|
||||||
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey!!))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val osXml: OutputStream
|
val osXml: OutputStream
|
||||||
@@ -105,8 +105,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
|
if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
|
||||||
val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml)
|
outputInnerHeader(mDatabaseKDBX, header!!, osXml)
|
||||||
ihOut.output()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outputDatabase(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)
|
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||||
private fun outputDatabase(outputStream: OutputStream) {
|
private fun outputDatabase(outputStream: OutputStream) {
|
||||||
|
|
||||||
@@ -282,9 +324,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
}
|
}
|
||||||
random.nextBytes(header.innerRandomStreamKey)
|
random.nextBytes(header.innerRandomStreamKey)
|
||||||
|
|
||||||
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
|
try {
|
||||||
if (randomStream == null) {
|
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
|
||||||
throw DatabaseOutputException("Invalid random cipher")
|
} catch (e: Exception) {
|
||||||
|
throw DatabaseOutputException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
|
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)))
|
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)
|
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||||
private fun writeBinary(binary : BinaryAttachment) {
|
private fun writeEntryBinary(binary : BinaryAttachment) {
|
||||||
val binaryLength = binary.length()
|
if (binary.length() > 0) {
|
||||||
if (binaryLength > 0) {
|
|
||||||
if (binary.isProtected) {
|
if (binary.isProtected) {
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
||||||
|
binary.getInputDataStream().use { inputStream ->
|
||||||
binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||||
val encoded = ByteArray(buffer.size)
|
val encoded = ByteArray(buffer.size)
|
||||||
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
|
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
|
||||||
val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
|
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
|
||||||
xml.text(charArray, 0, charArray.size)
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (binary.isCompressed) {
|
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
|
|
||||||
}
|
|
||||||
// Write the XML
|
// Write the XML
|
||||||
binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
binary.getInputDataStream().use { inputStream ->
|
||||||
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
|
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||||
xml.text(charArray, 0, charArray.size)
|
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)
|
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||||
private fun writeMetaBinaries() {
|
private fun writeMetaBinaries() {
|
||||||
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
|
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 ->
|
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
|
||||||
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
|
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)
|
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,13 +581,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
|
|
||||||
if (protect) {
|
if (protect) {
|
||||||
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
||||||
|
val data = value.toString().toByteArray()
|
||||||
val data = value.toString().toByteArray(charset("UTF-8"))
|
val dataLength = data.size
|
||||||
val valLength = data.size
|
if (data.isNotEmpty()) {
|
||||||
|
val encoded = ByteArray(dataLength)
|
||||||
if (valLength > 0) {
|
randomStream!!.processBytes(data, 0, dataLength, encoded, 0)
|
||||||
val encoded = ByteArray(valLength)
|
|
||||||
randomStream!!.processBytes(data, 0, valLength, encoded, 0)
|
|
||||||
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
|
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ import android.graphics.*
|
|||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import androidx.core.widget.ImageViewCompat
|
import androidx.core.widget.ImageViewCompat
|
||||||
@@ -87,6 +90,22 @@ class IconDrawableFactory {
|
|||||||
remoteViews.setImageViewBitmap(imageId, bitmap)
|
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
|
* 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)
|
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 icon: IconImage = IconImageStandard()
|
||||||
var username: String = ""
|
var username: String = ""
|
||||||
var password: String = ""
|
var password: String = ""
|
||||||
|
var creationTime: DateInstant = DateInstant()
|
||||||
|
var modificationTime: DateInstant = DateInstant()
|
||||||
var expires: Boolean = false
|
var expires: Boolean = false
|
||||||
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH
|
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH
|
||||||
var url: String = ""
|
var url: String = ""
|
||||||
@@ -55,6 +57,8 @@ class EntryInfo : Parcelable {
|
|||||||
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
|
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
|
||||||
username = parcel.readString() ?: username
|
username = parcel.readString() ?: username
|
||||||
password = parcel.readString() ?: password
|
password = parcel.readString() ?: password
|
||||||
|
creationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: creationTime
|
||||||
|
modificationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: modificationTime
|
||||||
expires = parcel.readInt() != 0
|
expires = parcel.readInt() != 0
|
||||||
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
|
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
|
||||||
url = parcel.readString() ?: url
|
url = parcel.readString() ?: url
|
||||||
@@ -74,6 +78,8 @@ class EntryInfo : Parcelable {
|
|||||||
parcel.writeParcelable(icon, flags)
|
parcel.writeParcelable(icon, flags)
|
||||||
parcel.writeString(username)
|
parcel.writeString(username)
|
||||||
parcel.writeString(password)
|
parcel.writeString(password)
|
||||||
|
parcel.writeParcelable(creationTime, flags)
|
||||||
|
parcel.writeParcelable(modificationTime, flags)
|
||||||
parcel.writeInt(if (expires) 1 else 0)
|
parcel.writeInt(if (expires) 1 else 0)
|
||||||
parcel.writeParcelable(expiryTime, flags)
|
parcel.writeParcelable(expiryTime, flags)
|
||||||
parcel.writeString(url)
|
parcel.writeString(url)
|
||||||
@@ -95,10 +101,6 @@ class EntryInfo : Parcelable {
|
|||||||
return customFields.lastOrNull { it.name == label } != null
|
return customFields.lastOrNull { it.name == label } != null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAutoGeneratedField(field: Field): Boolean {
|
|
||||||
return field.name == OTP_TOKEN_FIELD
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getGeneratedFieldValue(label: String): String {
|
fun getGeneratedFieldValue(label: String): String {
|
||||||
if (label == OTP_TOKEN_FIELD) {
|
if (label == OTP_TOKEN_FIELD) {
|
||||||
otpModel?.let {
|
otpModel?.let {
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.kunzisoft.keepass.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.text.format.Formatter
|
||||||
|
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility data class to get FileDatabaseInfo at a `t` time
|
||||||
|
*/
|
||||||
|
data class SnapFileDatabaseInfo(var fileUri: Uri?,
|
||||||
|
var exists: Boolean,
|
||||||
|
var lastModification: Long?,
|
||||||
|
var size: Long?): Parcelable {
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readParcelable(Uri::class.java.classLoader),
|
||||||
|
parcel.readByte() != 0.toByte(),
|
||||||
|
parcel.readValue(Long::class.java.classLoader) as? Long,
|
||||||
|
parcel.readValue(Long::class.java.classLoader) as? Long) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toString(context: Context): String {
|
||||||
|
val lastModificationString = DateFormat.getDateTimeInstance()
|
||||||
|
.format(Date(lastModification ?: 0))
|
||||||
|
return "$lastModificationString, " +
|
||||||
|
Formatter.formatFileSize(context, size ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeParcelable(fileUri, flags)
|
||||||
|
parcel.writeByte(if (exists) 1 else 0)
|
||||||
|
parcel.writeValue(lastModification)
|
||||||
|
parcel.writeValue(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is SnapFileDatabaseInfo) return false
|
||||||
|
|
||||||
|
if (fileUri != other.fileUri) return false
|
||||||
|
if (exists != other.exists) return false
|
||||||
|
if (lastModification != other.lastModification) return false
|
||||||
|
if (size != other.size) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = fileUri?.hashCode() ?: 0
|
||||||
|
result = 31 * result + exists.hashCode()
|
||||||
|
result = 31 * result + (lastModification?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (size?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<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.os.Parcelable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
|
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,6 +55,7 @@ class ClipboardEntryNotificationField : Parcelable {
|
|||||||
NotificationFieldId.UNKNOWN -> ""
|
NotificationFieldId.UNKNOWN -> ""
|
||||||
NotificationFieldId.USERNAME -> entryInfo?.username ?: ""
|
NotificationFieldId.USERNAME -> entryInfo?.username ?: ""
|
||||||
NotificationFieldId.PASSWORD -> entryInfo?.password ?: ""
|
NotificationFieldId.PASSWORD -> entryInfo?.password ?: ""
|
||||||
|
NotificationFieldId.OTP -> entryInfo?.getGeneratedFieldValue(OTP_TOKEN_FIELD) ?: ""
|
||||||
NotificationFieldId.FIELD_A,
|
NotificationFieldId.FIELD_A,
|
||||||
NotificationFieldId.FIELD_B,
|
NotificationFieldId.FIELD_B,
|
||||||
NotificationFieldId.FIELD_C -> entryInfo?.getGeneratedFieldValue(label) ?: ""
|
NotificationFieldId.FIELD_C -> entryInfo?.getGeneratedFieldValue(label) ?: ""
|
||||||
@@ -81,7 +83,7 @@ class ClipboardEntryNotificationField : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class NotificationFieldId {
|
enum class NotificationFieldId {
|
||||||
UNKNOWN, USERNAME, PASSWORD, FIELD_A, FIELD_B, FIELD_C;
|
UNKNOWN, USERNAME, PASSWORD, OTP, FIELD_A, FIELD_B, FIELD_C;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val anonymousFieldId: Array<NotificationFieldId>
|
val anonymousFieldId: Array<NotificationFieldId>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.content.Intent
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
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.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper.NEVER
|
import com.kunzisoft.keepass.timeout.TimeoutHelper.NEVER
|
||||||
@@ -250,6 +251,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
|||||||
val containsUsernameToCopy = entry.username.isNotEmpty()
|
val containsUsernameToCopy = entry.username.isNotEmpty()
|
||||||
val containsPasswordToCopy = entry.password.isNotEmpty()
|
val containsPasswordToCopy = entry.password.isNotEmpty()
|
||||||
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(context)
|
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(context)
|
||||||
|
val containsOTPToCopy = entry.containsCustomField(OTP_TOKEN_FIELD)
|
||||||
val containsExtraFieldToCopy = entry.customFields.isNotEmpty()
|
val containsExtraFieldToCopy = entry.customFields.isNotEmpty()
|
||||||
&& (entry.containsCustomFieldsNotProtected()
|
&& (entry.containsCustomFieldsNotProtected()
|
||||||
||
|
||
|
||||||
@@ -262,7 +264,10 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
|||||||
// If notifications enabled in settings
|
// If notifications enabled in settings
|
||||||
// Don't if application timeout
|
// Don't if application timeout
|
||||||
if (PreferencesUtil.isClipboardNotificationsEnable(context)) {
|
if (PreferencesUtil.isClipboardNotificationsEnable(context)) {
|
||||||
if (containsUsernameToCopy || containsPasswordToCopy || containsExtraFieldToCopy) {
|
if (containsUsernameToCopy
|
||||||
|
|| containsPasswordToCopy
|
||||||
|
|| containsOTPToCopy
|
||||||
|
|| containsExtraFieldToCopy) {
|
||||||
|
|
||||||
// username already copied, waiting for user's action before copy password.
|
// username already copied, waiting for user's action before copy password.
|
||||||
intent.action = ACTION_NEW_NOTIFICATION
|
intent.action = ACTION_NEW_NOTIFICATION
|
||||||
@@ -282,14 +287,22 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
|||||||
ClipboardEntryNotificationField.NotificationFieldId.PASSWORD,
|
ClipboardEntryNotificationField.NotificationFieldId.PASSWORD,
|
||||||
context.getString(R.string.entry_password)))
|
context.getString(R.string.entry_password)))
|
||||||
}
|
}
|
||||||
|
// Add OTP
|
||||||
|
if (containsOTPToCopy) {
|
||||||
|
notificationFields.add(
|
||||||
|
ClipboardEntryNotificationField(
|
||||||
|
ClipboardEntryNotificationField.NotificationFieldId.OTP,
|
||||||
|
OTP_TOKEN_FIELD))
|
||||||
|
}
|
||||||
// Add extra fields
|
// Add extra fields
|
||||||
if (containsExtraFieldToCopy) {
|
if (containsExtraFieldToCopy) {
|
||||||
try {
|
try {
|
||||||
var anonymousFieldNumber = 0
|
var anonymousFieldNumber = 0
|
||||||
entry.customFields.forEach { field ->
|
entry.customFields.forEach { field ->
|
||||||
//If value is not protected or allowed
|
//If value is not protected or allowed
|
||||||
if (!field.protectedValue.isProtected
|
if ((!field.protectedValue.isProtected
|
||||||
|| PreferencesUtil.allowCopyPasswordAndProtectedFields(context)) {
|
|| PreferencesUtil.allowCopyPasswordAndProtectedFields(context))
|
||||||
|
&& field.name != OTP_TOKEN_FIELD) {
|
||||||
notificationFields.add(
|
notificationFields.add(
|
||||||
ClipboardEntryNotificationField(
|
ClipboardEntryNotificationField(
|
||||||
ClipboardEntryNotificationField.NotificationFieldId.anonymousFieldId[anonymousFieldNumber],
|
ClipboardEntryNotificationField.NotificationFieldId.anonymousFieldId[anonymousFieldNumber],
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ package com.kunzisoft.keepass.notifications
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Binder
|
import android.os.*
|
||||||
import android.os.Bundle
|
import android.util.Log
|
||||||
import android.os.IBinder
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.GroupActivity
|
import com.kunzisoft.keepass.activities.GroupActivity
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
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.Node
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
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.ActionRunnable
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
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.DATABASE_STOP_TASK_ACTION
|
||||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||||
import com.kunzisoft.keepass.utils.closeDatabase
|
import com.kunzisoft.keepass.utils.closeDatabase
|
||||||
|
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@@ -65,6 +66,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
private var mAllowFinishAction = AtomicBoolean()
|
private var mAllowFinishAction = AtomicBoolean()
|
||||||
private var mActionRunning = false
|
private var mActionRunning = false
|
||||||
|
|
||||||
|
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
|
||||||
|
|
||||||
private var mIconId: Int = R.drawable.notification_ic_database_load
|
private var mIconId: Int = R.drawable.notification_ic_database_load
|
||||||
private var mTitleId: Int = R.string.database_opened
|
private var mTitleId: Int = R.string.database_opened
|
||||||
private var mMessageId: Int? = null
|
private var mMessageId: Int? = null
|
||||||
@@ -93,6 +96,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
mAllowFinishAction.set(false)
|
mAllowFinishAction.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
|
||||||
|
mDatabaseInfoListeners.add(databaseInfoListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
|
||||||
|
mDatabaseInfoListeners.remove(databaseInfoListener)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActionTaskListener {
|
interface ActionTaskListener {
|
||||||
@@ -101,6 +112,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
fun onStopAction(actionTask: String, result: ActionRunnable.Result)
|
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
|
* Force to call [ActionTaskListener.onStartAction] if the action is still running
|
||||||
*/
|
*/
|
||||||
@@ -112,6 +128,45 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkDatabaseInfo() {
|
||||||
|
mDatabase.fileUri?.let {
|
||||||
|
val previousDatabaseInfo = mSnapFileDatabaseInfo
|
||||||
|
val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||||
|
FileDatabaseInfo(applicationContext, it))
|
||||||
|
|
||||||
|
val oldDatabaseModification = previousDatabaseInfo?.lastModification
|
||||||
|
val newDatabaseModification = lastFileDatabaseInfo.lastModification
|
||||||
|
|
||||||
|
val conditionExists = previousDatabaseInfo != null
|
||||||
|
&& previousDatabaseInfo.exists != lastFileDatabaseInfo.exists
|
||||||
|
// To prevent dialog opening too often
|
||||||
|
val conditionLastModification = (oldDatabaseModification != null && newDatabaseModification != null
|
||||||
|
&& oldDatabaseModification < newDatabaseModification
|
||||||
|
&& mLastLocalSaveTime + 5000 < newDatabaseModification)
|
||||||
|
|
||||||
|
if (conditionExists || conditionLastModification) {
|
||||||
|
// Show the dialog only if it's real new info and not a delay after a save
|
||||||
|
Log.i(TAG, "Database file modified " +
|
||||||
|
"$previousDatabaseInfo != $lastFileDatabaseInfo ")
|
||||||
|
// Call listener to indicate a change in database info
|
||||||
|
if (previousDatabaseInfo != null) {
|
||||||
|
mDatabaseInfoListeners.forEach { listener ->
|
||||||
|
listener.onDatabaseInfoChanged(previousDatabaseInfo, lastFileDatabaseInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mSnapFileDatabaseInfo = lastFileDatabaseInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDatabaseInfo() {
|
||||||
|
mDatabase.fileUri?.let {
|
||||||
|
mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||||
|
FileDatabaseInfo(applicationContext, it))
|
||||||
|
Log.i(TAG, "Database file saved $mSnapFileDatabaseInfo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
super.onBind(intent)
|
super.onBind(intent)
|
||||||
return mActionTaskBinder
|
return mActionTaskBinder
|
||||||
@@ -138,6 +193,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
val actionRunnable: ActionRunnable? = when (intentAction) {
|
val actionRunnable: ActionRunnable? = when (intentAction) {
|
||||||
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
|
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
|
||||||
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
|
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
|
||||||
|
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask()
|
||||||
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
|
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
|
||||||
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
|
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
|
||||||
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
|
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
|
||||||
@@ -192,6 +248,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
actionTaskListener.onStopAction(intentAction!!, result)
|
actionTaskListener.onStopAction(intentAction!!, result)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
// Save the database info before performing action
|
||||||
|
if (intentAction == ACTION_DATABASE_LOAD_TASK) {
|
||||||
|
saveDatabaseInfo()
|
||||||
|
}
|
||||||
|
// Save the database info after performing save action
|
||||||
|
if (intentAction == ACTION_DATABASE_SAVE
|
||||||
|
|| intent?.getBooleanExtra(SAVE_DATABASE_KEY, false) == true) {
|
||||||
|
mDatabase.fileUri?.let {
|
||||||
|
val newSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||||
|
FileDatabaseInfo(applicationContext, it))
|
||||||
|
mLastLocalSaveTime = System.currentTimeMillis()
|
||||||
|
mSnapFileDatabaseInfo = newSnapFileDatabaseInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
removeIntentData(intent)
|
removeIntentData(intent)
|
||||||
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
|
||||||
@@ -214,7 +284,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
|
|
||||||
return when (intentAction) {
|
return when (intentAction) {
|
||||||
ACTION_DATABASE_LOAD_TASK, null -> {
|
ACTION_DATABASE_LOAD_TASK,
|
||||||
|
ACTION_DATABASE_RELOAD_TASK,
|
||||||
|
null -> {
|
||||||
START_STICKY
|
START_STICKY
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -248,7 +320,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
else -> {
|
else -> {
|
||||||
when (intentAction) {
|
when (intentAction) {
|
||||||
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
|
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
|
ACTION_DATABASE_SAVE -> R.string.saving_database
|
||||||
else -> {
|
else -> {
|
||||||
R.string.command_execution
|
R.string.command_execution
|
||||||
@@ -258,13 +331,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
|
|
||||||
mMessageId = when (intentAction) {
|
mMessageId = when (intentAction) {
|
||||||
ACTION_DATABASE_LOAD_TASK -> null
|
ACTION_DATABASE_LOAD_TASK,
|
||||||
|
ACTION_DATABASE_RELOAD_TASK -> null
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
mWarningId =
|
mWarningId =
|
||||||
if (!saveAction
|
if (!saveAction
|
||||||
|| intentAction == ACTION_DATABASE_LOAD_TASK)
|
|| intentAction == ACTION_DATABASE_LOAD_TASK
|
||||||
|
|| intentAction == ACTION_DATABASE_RELOAD_TASK)
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
R.string.do_not_kill_app
|
R.string.do_not_kill_app
|
||||||
@@ -342,9 +417,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
* Execute action with a coroutine
|
* Execute action with a coroutine
|
||||||
*/
|
*/
|
||||||
private suspend fun executeAction(progressTaskUpdater: ProgressTaskUpdater,
|
private suspend fun executeAction(progressTaskUpdater: ProgressTaskUpdater,
|
||||||
onPreExecute: () -> Unit,
|
onPreExecute: () -> Unit,
|
||||||
onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
|
onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
|
||||||
onPostExecute: (result: ActionRunnable.Result) -> Unit) {
|
onPostExecute: (result: ActionRunnable.Result) -> Unit) {
|
||||||
mAllowFinishAction.set(false)
|
mAllowFinishAction.set(false)
|
||||||
|
|
||||||
TimeoutHelper.temporarilyDisableTimeout()
|
TimeoutHelper.temporarilyDisableTimeout()
|
||||||
@@ -465,6 +540,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? {
|
private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? {
|
||||||
return if (intent.hasExtra(DATABASE_URI_KEY)
|
return if (intent.hasExtra(DATABASE_URI_KEY)
|
||||||
&& intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY)
|
&& intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY)
|
||||||
@@ -770,6 +856,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
|
|
||||||
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
|
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
|
||||||
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_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_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
|
||||||
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_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"
|
const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK"
|
||||||
@@ -822,6 +909,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time
|
const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time
|
||||||
const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time
|
const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time
|
||||||
|
|
||||||
|
private var mSnapFileDatabaseInfo: SnapFileDatabaseInfo? = null
|
||||||
|
private var mLastLocalSaveTime: Long = 0
|
||||||
|
|
||||||
fun getListNodesFromBundle(database: Database, bundle: Bundle): List<Node> {
|
fun getListNodesFromBundle(database: Database, bundle: Bundle): List<Node> {
|
||||||
val nodesAction = ArrayList<Node>()
|
val nodesAction = ArrayList<Node>()
|
||||||
bundle.getParcelableArrayList<NodeId<*>>(GROUPS_ID_KEY)?.forEach {
|
bundle.getParcelableArrayList<NodeId<*>>(GROUPS_ID_KEY)?.forEach {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.otp
|
package com.kunzisoft.keepass.otp
|
||||||
|
|
||||||
import com.kunzisoft.keepass.model.OtpModel
|
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.Base32
|
||||||
import org.apache.commons.codec.binary.Base64
|
import org.apache.commons.codec.binary.Base64
|
||||||
import org.apache.commons.codec.binary.Hex
|
import org.apache.commons.codec.binary.Hex
|
||||||
@@ -150,16 +151,16 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
|
|||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
fun setBase32Secret(secret: String) {
|
fun setBase32Secret(secret: String) {
|
||||||
if (isValidBase32(secret))
|
if (isValidBase32(secret)) {
|
||||||
otpModel.secret = Base32().decode(replaceBase32Chars(secret).toByteArray())
|
otpModel.secret = Base32().decode(replaceBase32Chars(secret))
|
||||||
else
|
} else
|
||||||
throw IllegalArgumentException()
|
throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
fun setBase64Secret(secret: String) {
|
fun setBase64Secret(secret: String) {
|
||||||
if (isValidBase64(secret))
|
if (isValidBase64(secret))
|
||||||
otpModel.secret = Base64().decode(secret.toByteArray())
|
otpModel.secret = Base64().decode(secret)
|
||||||
else
|
else
|
||||||
throw IllegalArgumentException()
|
throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
@@ -208,38 +209,24 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
|
|||||||
|
|
||||||
fun isValidBase32(secret: String): Boolean {
|
fun isValidBase32(secret: String): Boolean {
|
||||||
val secretChars = replaceBase32Chars(secret)
|
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 {
|
fun isValidBase64(secret: String): Boolean {
|
||||||
// TODO replace base 64 chars
|
// TODO replace base 64 chars
|
||||||
return secret.isNotEmpty() && checkBase64Secret(secret)
|
return secret.isNotEmpty()
|
||||||
}
|
&& (Pattern.matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", 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(), "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun replaceBase32Chars(parameter: String): String {
|
fun replaceBase32Chars(parameter: String): String {
|
||||||
// Add 'A' at end if not Base32 length
|
// Add padding '=' at end if not Base32 length
|
||||||
var parameterNewSize = removeSpaceChars(parameter.toUpperCase(Locale.ENGLISH))
|
var parameterNewSize = parameter.toUpperCase(Locale.ENGLISH).removeSpaceChars()
|
||||||
while (parameterNewSize.length % 8 != 0) {
|
while (parameterNewSize.length % 8 != 0) {
|
||||||
parameterNewSize += 'A'
|
parameterNewSize += '='
|
||||||
}
|
}
|
||||||
return 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 android.util.Log
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.model.Field
|
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.otp.TokenCalculator.*
|
||||||
|
import com.kunzisoft.keepass.utils.StringUtil.removeLineChars
|
||||||
|
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
@@ -57,13 +57,25 @@ object OtpEntryFields {
|
|||||||
private const val DIGITS_KEY = "size"
|
private const val DIGITS_KEY = "size"
|
||||||
private const val STEP_KEY = "step"
|
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_FIELD = "HmacOtp-Secret"
|
||||||
private const val HMACOTP_SECRET_HEX_FIELD = "HmacOtp-Secret-Hex"
|
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_BASE32_FIELD = "HmacOtp-Secret-Base32"
|
||||||
private const val HMACOTP_SECRET_BASE64_FIELD = "HmacOtp-Secret-Base64"
|
private const val HMACOTP_SECRET_BASE64_FIELD = "HmacOtp-Secret-Base64"
|
||||||
private const val HMACOTP_SECRET_COUNTER_FIELD = "HmacOtp-Counter"
|
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)
|
// Custom fields (maybe from plugin)
|
||||||
private const val TOTP_SEED_FIELD = "TOTP Seed"
|
private const val TOTP_SEED_FIELD = "TOTP Seed"
|
||||||
private const val TOTP_SETTING_FIELD = "TOTP Settings"
|
private const val TOTP_SETTING_FIELD = "TOTP Settings"
|
||||||
@@ -85,14 +97,17 @@ object OtpEntryFields {
|
|||||||
// OTP (HOTP/TOTP) from URL and field from KeePassXC
|
// OTP (HOTP/TOTP) from URL and field from KeePassXC
|
||||||
if (parseOTPUri(getField, otpElement))
|
if (parseOTPUri(getField, otpElement))
|
||||||
return otpElement
|
return otpElement
|
||||||
|
// TOTP from KeePass 2.47
|
||||||
|
if (parseTOTPFromOfficialField(getField, otpElement))
|
||||||
|
return otpElement
|
||||||
// TOTP from key values (maybe plugin or old KeePassXC)
|
// TOTP from key values (maybe plugin or old KeePassXC)
|
||||||
if (parseTOTPKeyValues(getField, otpElement))
|
if (parseTOTPKeyValues(getField, otpElement))
|
||||||
return otpElement
|
return otpElement
|
||||||
// TOTP from custom field
|
// TOTP from custom field
|
||||||
if (parseTOTPFromField(getField, otpElement))
|
if (parseTOTPFromPluginField(getField, otpElement))
|
||||||
return otpElement
|
return otpElement
|
||||||
// HOTP fields from KeePass 2
|
// HOTP fields from KeePass 2
|
||||||
if (parseHOTPFromField(getField, otpElement))
|
if (parseHOTPFromOfficialField(getField, otpElement))
|
||||||
return otpElement
|
return otpElement
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -126,7 +141,7 @@ object OtpEntryFields {
|
|||||||
private fun parseOTPUri(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
|
private fun parseOTPUri(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
|
||||||
val otpPlainText = getField(OTP_FIELD)
|
val otpPlainText = getField(OTP_FIELD)
|
||||||
if (otpPlainText != null && otpPlainText.isNotEmpty() && isOTPUri(otpPlainText)) {
|
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)) {
|
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) {
|
||||||
Log.e(TAG, "Invalid or missing scheme in uri")
|
Log.e(TAG, "Invalid or missing scheme in uri")
|
||||||
@@ -159,16 +174,16 @@ object OtpEntryFields {
|
|||||||
if (nameParam != null && nameParam.isNotEmpty()) {
|
if (nameParam != null && nameParam.isNotEmpty()) {
|
||||||
val userIdArray = nameParam.split(":", "%3A")
|
val userIdArray = nameParam.split(":", "%3A")
|
||||||
if (userIdArray.size > 1) {
|
if (userIdArray.size > 1) {
|
||||||
otpElement.issuer = removeLineChars(userIdArray[0])
|
otpElement.issuer = userIdArray[0].removeLineChars()
|
||||||
otpElement.name = removeLineChars(userIdArray[1])
|
otpElement.name = userIdArray[1].removeLineChars()
|
||||||
} else {
|
} else {
|
||||||
otpElement.name = removeLineChars(nameParam)
|
otpElement.name = nameParam.removeLineChars()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val issuerParam = uri.getQueryParameter(ISSUER_URL_PARAM)
|
val issuerParam = uri.getQueryParameter(ISSUER_URL_PARAM)
|
||||||
if (issuerParam != null && issuerParam.isNotEmpty())
|
if (issuerParam != null && issuerParam.isNotEmpty())
|
||||||
otpElement.issuer = removeLineChars(issuerParam)
|
otpElement.issuer = issuerParam.removeLineChars()
|
||||||
|
|
||||||
val secretParam = uri.getQueryParameter(SECRET_URL_PARAM)
|
val secretParam = uri.getQueryParameter(SECRET_URL_PARAM)
|
||||||
if (secretParam != null && secretParam.isNotEmpty()) {
|
if (secretParam != null && secretParam.isNotEmpty()) {
|
||||||
@@ -247,8 +262,9 @@ object OtpEntryFields {
|
|||||||
encodeParameter(username)
|
encodeParameter(username)
|
||||||
else
|
else
|
||||||
encodeParameter(otpElement.name)
|
encodeParameter(otpElement.name)
|
||||||
|
val secret = encodeParameter(otpElement.getBase32Secret())
|
||||||
val uriString = StringBuilder("otpauth://$otpAuthority/$issuer%3A$accountName" +
|
val uriString = StringBuilder("otpauth://$otpAuthority/$issuer%3A$accountName" +
|
||||||
"?$SECRET_URL_PARAM=${otpElement.getBase32Secret()}" +
|
"?$SECRET_URL_PARAM=${secret}" +
|
||||||
"&$counterOrPeriodLabel=$counterOrPeriodValue" +
|
"&$counterOrPeriodLabel=$counterOrPeriodValue" +
|
||||||
"&$DIGITS_URL_PARAM=${otpElement.digits}" +
|
"&$DIGITS_URL_PARAM=${otpElement.digits}" +
|
||||||
"&$ISSUER_URL_PARAM=$issuer")
|
"&$ISSUER_URL_PARAM=$issuer")
|
||||||
@@ -262,7 +278,40 @@ object OtpEntryFields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun encodeParameter(parameter: String): String {
|
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 {
|
private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
|
||||||
@@ -290,7 +339,7 @@ object OtpEntryFields {
|
|||||||
return false
|
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
|
val seedField = getField(TOTP_SEED_FIELD) ?: return false
|
||||||
try {
|
try {
|
||||||
otpElement.setBase32Secret(seedField)
|
otpElement.setBase32Secret(seedField)
|
||||||
@@ -316,7 +365,7 @@ object OtpEntryFields {
|
|||||||
return true
|
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 secretField = getField(HMACOTP_SECRET_FIELD)
|
||||||
val secretHexField = getField(HMACOTP_SECRET_HEX_FIELD)
|
val secretHexField = getField(HMACOTP_SECRET_HEX_FIELD)
|
||||||
val secretBase32Field = getField(HMACOTP_SECRET_BASE32_FIELD)
|
val secretBase32Field = getField(HMACOTP_SECRET_BASE32_FIELD)
|
||||||
@@ -382,25 +431,43 @@ object OtpEntryFields {
|
|||||||
val totpSeedField = Field(TOTP_SEED_FIELD)
|
val totpSeedField = Field(TOTP_SEED_FIELD)
|
||||||
val totpSettingField = Field(TOTP_SETTING_FIELD)
|
val totpSettingField = Field(TOTP_SETTING_FIELD)
|
||||||
val hmacOtpSecretField = Field(HMACOTP_SECRET_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 hmacOtpSecretBase32Field = Field(HMACOTP_SECRET_BASE32_FIELD)
|
||||||
val hmacOtpSecretBase64Field = Field(HMACOTP_SECRET_BASE64_FIELD)
|
val hmacOtpSecretBase64Field = Field(HMACOTP_SECRET_BASE64_FIELD)
|
||||||
val hmacOtpSecretCounterField = Field(HMACOTP_SECRET_COUNTER_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(otpField)
|
||||||
newCustomFields.remove(totpSeedField)
|
newCustomFields.remove(totpSeedField)
|
||||||
newCustomFields.remove(totpSettingField)
|
newCustomFields.remove(totpSettingField)
|
||||||
newCustomFields.remove(hmacOtpSecretField)
|
newCustomFields.remove(hmacOtpSecretField)
|
||||||
newCustomFields.remove(hmacOtpSecretHewField)
|
newCustomFields.remove(hmacOtpSecretHexField)
|
||||||
newCustomFields.remove(hmacOtpSecretBase32Field)
|
newCustomFields.remove(hmacOtpSecretBase32Field)
|
||||||
newCustomFields.remove(hmacOtpSecretBase64Field)
|
newCustomFields.remove(hmacOtpSecretBase64Field)
|
||||||
newCustomFields.remove(hmacOtpSecretCounterField)
|
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
|
// Empty auto generated OTP Token field
|
||||||
if (fieldsToParse.contains(otpField)
|
if (fieldsToParse.contains(otpField)
|
||||||
|| fieldsToParse.contains(totpSeedField)
|
|| fieldsToParse.contains(totpSeedField)
|
||||||
|| fieldsToParse.contains(hmacOtpSecretField)
|
|| fieldsToParse.contains(hmacOtpSecretField)
|
||||||
|| fieldsToParse.contains(hmacOtpSecretHewField)
|
|| fieldsToParse.contains(hmacOtpSecretHexField)
|
||||||
|| fieldsToParse.contains(hmacOtpSecretBase32Field)
|
|| fieldsToParse.contains(hmacOtpSecretBase32Field)
|
||||||
|| fieldsToParse.contains(hmacOtpSecretBase64Field)
|
|| fieldsToParse.contains(hmacOtpSecretBase64Field)
|
||||||
|
|| fieldsToParse.contains(timeOtpSecretField)
|
||||||
|
|| fieldsToParse.contains(timeOtpSecretHexField)
|
||||||
|
|| fieldsToParse.contains(timeOtpSecretBase32Field)
|
||||||
|
|| fieldsToParse.contains(timeOtpSecretBase64Field)
|
||||||
)
|
)
|
||||||
newCustomFields.add(Field(OTP_TOKEN_FIELD))
|
newCustomFields.add(Field(OTP_TOKEN_FIELD))
|
||||||
return newCustomFields
|
return newCustomFields
|
||||||
|
|||||||
@@ -19,10 +19,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.settings
|
package com.kunzisoft.keepass.settings
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.SwitchPreference
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistAppIdPreferenceDialogFragmentCompat
|
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistAppIdPreferenceDialogFragmentCompat
|
||||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistWebDomainPreferenceDialogFragmentCompat
|
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistWebDomainPreferenceDialogFragmentCompat
|
||||||
@@ -32,6 +34,11 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
// Load the preferences from an XML resource
|
// Load the preferences from an XML resource
|
||||||
setPreferencesFromResource(R.xml.preferences_autofill, rootKey)
|
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?) {
|
override fun onDisplayPreferenceDialog(preference: Preference?) {
|
||||||
|
|||||||
@@ -103,6 +103,6 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Callback {
|
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)
|
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_reload_database -> {
|
||||||
|
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseReload(false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// Check the time lock before launching settings
|
// Check the time lock before launching settings
|
||||||
|
|||||||
@@ -436,13 +436,18 @@ object PreferencesUtil {
|
|||||||
context.resources.getBoolean(R.bool.autofill_close_database_default))
|
context.resources.getBoolean(R.bool.autofill_close_database_default))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun isAutofillAutoSearchEnable(context: Context): Boolean {
|
fun isAutofillAutoSearchEnable(context: Context): Boolean {
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
|
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
|
||||||
context.resources.getBoolean(R.bool.autofill_auto_search_default))
|
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 {
|
fun isAutofillSaveSearchInfoEnable(context: Context): Boolean {
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
return prefs.getBoolean(context.getString(R.string.autofill_save_search_info_key),
|
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.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.settings
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.backup.BackupManager
|
import android.app.backup.BackupManager
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
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.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.view.showActionError
|
import com.kunzisoft.keepass.view.showActionError
|
||||||
|
|
||||||
@@ -95,12 +95,28 @@ open class SettingsActivity
|
|||||||
backupManager = BackupManager(this)
|
backupManager = BackupManager(this)
|
||||||
|
|
||||||
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
|
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
|
||||||
// Call result in fragment
|
when (actionTask) {
|
||||||
(supportFragmentManager
|
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
|
||||||
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
|
// Reload the current activity
|
||||||
?.onProgressDialogThreadResult(actionTask, result)
|
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)
|
hideOrShowLockButton(NestedSettingsFragment.Screen.APPLICATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replaceFragment(key: NestedSettingsFragment.Screen) {
|
private fun replaceFragment(key: NestedSettingsFragment.Screen, reload: Boolean) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction().apply {
|
||||||
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
if (reload) {
|
||||||
|
setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out,
|
||||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
R.anim.slide_in_left, R.anim.slide_out_right)
|
||||||
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
|
} else {
|
||||||
.addToBackStack(TAG_NESTED)
|
setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||||
.commit()
|
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)
|
toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key)
|
||||||
|
// To reload the current screen
|
||||||
|
intent.putExtra(FRAGMENT_ARG, key.name)
|
||||||
hideOrShowLockButton(key)
|
hideOrShowLockButton(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen) {
|
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
|
||||||
if (mTimeoutEnable)
|
if (mTimeoutEnable)
|
||||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
||||||
replaceFragment(key)
|
replaceFragment(key, reload)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
replaceFragment(key)
|
replaceFragment(key, reload)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@@ -226,6 +250,7 @@ open class SettingsActivity
|
|||||||
private const val SHOW_LOCK = "SHOW_LOCK"
|
private const val SHOW_LOCK = "SHOW_LOCK"
|
||||||
private const val TITLE_KEY = "TITLE_KEY"
|
private const val TITLE_KEY = "TITLE_KEY"
|
||||||
private const val TAG_NESTED = "TAG_NESTED"
|
private const val TAG_NESTED = "TAG_NESTED"
|
||||||
|
private const val FRAGMENT_ARG = "FRAGMENT_ARG"
|
||||||
|
|
||||||
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
|
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
|
||||||
val intent = Intent(activity, SettingsActivity::class.java)
|
val intent = Intent(activity, SettingsActivity::class.java)
|
||||||
|
|||||||
@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
|
|||||||
cancelAll()
|
cancelAll()
|
||||||
}
|
}
|
||||||
// Clear data
|
// Clear data
|
||||||
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this))
|
Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
|
||||||
}
|
}
|
||||||
14
app/src/main/java/com/kunzisoft/keepass/utils/StringUtil.kt
Normal file
14
app/src/main/java/com/kunzisoft/keepass/utils/StringUtil.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.kunzisoft.keepass.utils
|
||||||
|
|
||||||
|
object StringUtil {
|
||||||
|
|
||||||
|
fun String.removeLineChars(): String {
|
||||||
|
return this.replace("[\\r|\\n|\\t|\\u00A0]+".toRegex(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.removeSpaceChars(): String {
|
||||||
|
return this.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
|
||||||
|
}
|
||||||
@@ -67,7 +67,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
|
|
||||||
private val creationDateView: TextView
|
private val creationDateView: TextView
|
||||||
private val modificationDateView: TextView
|
private val modificationDateView: TextView
|
||||||
private val lastAccessDateView: TextView
|
|
||||||
private val expiresImageView: ImageView
|
private val expiresImageView: ImageView
|
||||||
private val expiresDateView: TextView
|
private val expiresDateView: TextView
|
||||||
|
|
||||||
@@ -117,7 +116,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
|
|
||||||
creationDateView = findViewById(R.id.entry_created)
|
creationDateView = findViewById(R.id.entry_created)
|
||||||
modificationDateView = findViewById(R.id.entry_modified)
|
modificationDateView = findViewById(R.id.entry_modified)
|
||||||
lastAccessDateView = findViewById(R.id.entry_accessed)
|
|
||||||
expiresImageView = findViewById(R.id.entry_expires_image)
|
expiresImageView = findViewById(R.id.entry_expires_image)
|
||||||
expiresDateView = findViewById(R.id.entry_expires_date)
|
expiresDateView = findViewById(R.id.entry_expires_date)
|
||||||
|
|
||||||
@@ -258,20 +256,13 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
modificationDateView.text = date.getDateTimeString(resources)
|
modificationDateView.text = date.getDateTimeString(resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignLastAccessDate(date: DateInstant) {
|
fun setExpires(isExpires: Boolean, expiryTime: DateInstant) {
|
||||||
lastAccessDateView.text = date.getDateTimeString(resources)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setExpires(isExpires: Boolean) {
|
|
||||||
expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE
|
expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE
|
||||||
}
|
expiresDateView.text = if (isExpires) {
|
||||||
|
expiryTime.getDateTimeString(resources)
|
||||||
fun assignExpiresDate(date: DateInstant) {
|
} else {
|
||||||
assignExpiresDate(date.getDateTimeString(resources))
|
resources.getString(R.string.never)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignExpiresDate(constString: String) {
|
|
||||||
expiresDateView.text = constString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignUUID(uuid: UUID) {
|
fun assignUUID(uuid: UUID) {
|
||||||
@@ -279,7 +270,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
uuidReferenceView.text = UuidUtil.toHexString(uuid)
|
uuidReferenceView.text = UuidUtil.toHexString(uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) {
|
fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) {
|
||||||
passwordFieldView.hiddenProtectedValue = hiddenProtectedValue
|
passwordFieldView.hiddenProtectedValue = hiddenProtectedValue
|
||||||
// Hidden style for custom fields
|
// Hidden style for custom fields
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
@@ -20,7 +19,6 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
|
|||||||
|
|
||||||
private val keyFileNameInputLayout: TextInputLayout
|
private val keyFileNameInputLayout: TextInputLayout
|
||||||
private val keyFileNameView: TextView
|
private val keyFileNameView: TextView
|
||||||
private val keyFileOpenView: ImageView
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
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)
|
keyFileNameInputLayout = findViewById(R.id.input_entry_keyfile)
|
||||||
keyFileNameView = findViewById(R.id.keyfile_name)
|
keyFileNameView = findViewById(R.id.keyfile_name)
|
||||||
keyFileOpenView = findViewById(R.id.keyfile_open_button)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setOnClickListener(l: OnClickListener?) {
|
override fun setOnClickListener(l: OnClickListener?) {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
@@ -58,7 +57,11 @@ class FileDatabaseInfo : Serializable {
|
|||||||
}
|
}
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun getModificationString(): String? {
|
fun getLastModification(): Long? {
|
||||||
|
return documentFile?.lastModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastModificationString(): String? {
|
||||||
return documentFile?.lastModified()?.let {
|
return documentFile?.lastModified()?.let {
|
||||||
if (it != 0L) {
|
if (it != 0L) {
|
||||||
DateFormat.getDateTimeInstance()
|
DateFormat.getDateTimeInstance()
|
||||||
@@ -69,6 +72,10 @@ class FileDatabaseInfo : Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSize(): Long? {
|
||||||
|
return documentFile?.length()
|
||||||
|
}
|
||||||
|
|
||||||
fun getSizeString(): String? {
|
fun getSizeString(): String? {
|
||||||
return documentFile?.let {
|
return documentFile?.let {
|
||||||
Formatter.formatFileSize(context, it.length())
|
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:id="@+id/entry_extra_field_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignTop="@+id/entry_extra_field_value_container"
|
||||||
android:src="@drawable/ic_more_white_24dp"
|
android:src="@drawable/ic_more_white_24dp"
|
||||||
android:contentDescription="@string/menu_edit"
|
android:contentDescription="@string/menu_edit"
|
||||||
style="@style/KeepassDXStyle.ImageButton.Simple"/>
|
style="@style/KeepassDXStyle.ImageButton.Simple"/>
|
||||||
|
|||||||
@@ -175,12 +175,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/entry_accessed"
|
android:text="@string/entry_accessed"
|
||||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
|
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 -->
|
<!-- Expires -->
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="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
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/keyfile_name"
|
android:id="@+id/keyfile_name"
|
||||||
@@ -33,16 +33,4 @@
|
|||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -22,6 +22,6 @@
|
|||||||
<item android:id="@+id/menu_contribute"
|
<item android:id="@+id/menu_contribute"
|
||||||
android:icon="@drawable/ic_heart_white_24dp"
|
android:icon="@drawable/ic_heart_white_24dp"
|
||||||
android:title="@string/contribute"
|
android:title="@string/contribute"
|
||||||
android:orderInCategory="95"
|
android:orderInCategory="99"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -24,4 +24,9 @@
|
|||||||
android:title="@string/menu_save_database"
|
android:title="@string/menu_save_database"
|
||||||
android:orderInCategory="95"
|
android:orderInCategory="95"
|
||||||
app:showAsAction="ifRoom" />
|
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>
|
</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
|
Czech translation by Jan Vaněk
|
||||||
--><resources>
|
--><resources>
|
||||||
<string name="homepage">Domovská stránka</string>
|
<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="accept">Přijmout</string>
|
||||||
<string name="add_entry">Přidat záznam</string>
|
<string name="add_entry">Přidat záznam</string>
|
||||||
<string name="add_group">Přidat skupinu</string>
|
<string name="add_group">Přidat skupinu</string>
|
||||||
<string name="encryption_algorithm">Šifrovací algoritmus</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="app_timeout_summary">Doba nečinnosti, po které se aplikace zamkne</string>
|
||||||
<string name="application">Aplikace</string>
|
<string name="application">Aplikace</string>
|
||||||
<string name="menu_app_settings">Nastavení 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">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_error_clear">Nelze vyprázdnit schránku</string>
|
||||||
<string name="clipboard_timeout">Časový limit schránky</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="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="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="default_checkbox">Použít jako výchozí databázi</string>
|
||||||
<string name="digits">Číslice</string>
|
<string name="digits">Číslice</string>
|
||||||
<string name="select_database_file">Otevřít existující databázi</string>
|
<string name="select_database_file">Otevřít existující databázi</string>
|
||||||
<string name="entry_accessed">Poslední přístup</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_notes">Poznámky</string>
|
||||||
<string name="entry_confpassword">Potvrďte heslo</string>
|
<string name="entry_confpassword">Potvrďte heslo</string>
|
||||||
<string name="entry_created">Vytvořeno</string>
|
<string name="entry_created">Vytvořeno</string>
|
||||||
@@ -55,27 +55,27 @@
|
|||||||
<string name="entry_password">Heslo</string>
|
<string name="entry_password">Heslo</string>
|
||||||
<string name="save">Uložit</string>
|
<string name="save">Uložit</string>
|
||||||
<string name="entry_title">Název</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="entry_user_name">Uživatelské jméno</string>
|
||||||
<string name="error_arc4">Arcfour proudová šifra není podporována.</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_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_file_not_create">Soubor se nepodařilo vytvořit</string>
|
||||||
<string name="error_invalid_db">Databázi nelze číst.</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_invalid_path">Ujistěte se, že cesta je správná.</string>
|
||||||
<string name="error_no_name">Zadejte jméno.</string>
|
<string name="error_no_name">Zadejte jméno.</string>
|
||||||
<string name="error_nokeyfile">Vyberte soubor s klíčem.</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_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_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_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_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="error_wrong_length">Do pole „Délka“ zadejte celé kladné číslo.</string>
|
||||||
<string name="field_name">Název pole</string>
|
<string name="field_name">Název kolonky</string>
|
||||||
<string name="field_value">Hodnota pole</string>
|
<string name="field_value">Hodnota kolonky</string>
|
||||||
<string name="file_browser">Správce souborů</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_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_group_name">Název skupiny</string>
|
||||||
<string name="hint_keyfile">Soubor s klíčem</string>
|
<string name="hint_keyfile">Soubor s klíčem</string>
|
||||||
<string name="hint_length">Délka</string>
|
<string name="hint_length">Délka</string>
|
||||||
@@ -83,8 +83,8 @@
|
|||||||
<string name="password">Heslo</string>
|
<string name="password">Heslo</string>
|
||||||
<string name="invalid_credentials">Nebylo možno načíst autentizační údaje.</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_algorithm">Nesprávný algoritmus.</string>
|
||||||
<string name="invalid_db_sig">Nedaří se rozpoznat formát databáze.</string>
|
<string name="invalid_db_sig">Nepodařilo se rozpoznat formát databáze.</string>
|
||||||
<string name="keyfile_is_empty">Soubor s klíčem je prázdný.</string>
|
<string name="keyfile_is_empty">Soubor klíče je prázdný.</string>
|
||||||
<string name="length">Délka</string>
|
<string name="length">Délka</string>
|
||||||
<string name="list_size_title">Velikost položek seznamu</string>
|
<string name="list_size_title">Velikost položek seznamu</string>
|
||||||
<string name="list_size_summary">Velikost textu v seznamu prvků</string>
|
<string name="list_size_summary">Velikost textu v seznamu prvků</string>
|
||||||
@@ -97,28 +97,28 @@
|
|||||||
<string name="settings">Nastavení</string>
|
<string name="settings">Nastavení</string>
|
||||||
<string name="menu_database_settings">Nastavení databáze</string>
|
<string name="menu_database_settings">Nastavení databáze</string>
|
||||||
<string name="menu_delete">Smazat</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_edit">Upravit</string>
|
||||||
<string name="menu_hide_password">Skrýt heslo</string>
|
<string name="menu_hide_password">Skrýt heslo</string>
|
||||||
<string name="menu_lock">Zamknout databázi</string>
|
<string name="menu_lock">Zamknout databázi</string>
|
||||||
<string name="menu_open">Otevřít</string>
|
<string name="menu_open">Otevřít</string>
|
||||||
<string name="menu_search">Hledat</string>
|
<string name="menu_search">Hledat</string>
|
||||||
<string name="menu_showpass">Ukaž heslo</string>
|
<string name="menu_showpass">Ukázat heslo</string>
|
||||||
<string name="menu_url">Jít na URL</string>
|
<string name="menu_url">Přejít na URL</string>
|
||||||
<string name="minus">Mínus</string>
|
<string name="minus">Mínus</string>
|
||||||
<string name="never">Nikdy</string>
|
<string name="never">Nikdy</string>
|
||||||
<string name="no_results">Žádné výsledky hledání</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="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_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="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_create">Zakládám novou databázi…</string>
|
||||||
<string name="progress_title">Zpracování…</string>
|
<string name="progress_title">Pracuji…</string>
|
||||||
<string name="protection">Ochrana</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="content_description_remove_from_list">Odstranit</string>
|
||||||
<string name="encryption_rijndael">Rijndael (AES)</string>
|
<string name="encryption_rijndael">Rijndael (AES)</string>
|
||||||
<string name="root">Kořen</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="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="saving_database">Ukládám databázi…</string>
|
||||||
<string name="space">Místo</string>
|
<string name="space">Místo</string>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
<string name="version_label">Verze %1$s</string>
|
<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.
|
<string name="education_unlock_summary">Databázi odemknete zadáním hesla a/nebo souboru s klíčem.
|
||||||
\n
|
\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">
|
<string-array name="timeout_options">
|
||||||
<item>5 sekund</item>
|
<item>5 sekund</item>
|
||||||
<item>10 sekund</item>
|
<item>10 sekund</item>
|
||||||
@@ -155,41 +155,41 @@
|
|||||||
<string name="encryption">Šifrování</string>
|
<string name="encryption">Šifrování</string>
|
||||||
<string name="key_derivation_function">Funkce pro tvorbu klíče</string>
|
<string name="key_derivation_function">Funkce pro tvorbu klíče</string>
|
||||||
<string name="extended_ASCII">Rozšířené ASCII</string>
|
<string name="extended_ASCII">Rozšířené ASCII</string>
|
||||||
<string name="allow">Umožnit</string>
|
<string name="allow">Povolit</string>
|
||||||
<string name="error_load_database">Databázi se nedaří načíst.</string>
|
<string name="error_load_database">Databázi se nepodařilo 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_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 nedaří zapnout.</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="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="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_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="copy_field">Kopie %1$s</string>
|
||||||
<string name="menu_form_filling_settings">Vyplňování formulářů</string>
|
<string name="menu_form_filling_settings">Vyplňování formulářů</string>
|
||||||
<string name="menu_copy">Zkopírovat</string>
|
<string name="menu_copy">Zkopírovat</string>
|
||||||
<string name="menu_move">Přesunout</string>
|
<string name="menu_move">Přesunout</string>
|
||||||
<string name="menu_paste">Vložit</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_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="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="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="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 skrze odvozovací funkci klíče s náhodně přidanou složkou, tzv. solí.</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">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="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">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_menu">Seřadit</string>
|
||||||
<string name="sort_ascending">Nejnižší první ↓</string>
|
<string name="sort_ascending">Nejnižší první ↓</string>
|
||||||
<string name="sort_groups_before">Skupiny první</string>
|
<string name="sort_groups_before">Skupiny první</string>
|
||||||
<string name="sort_recycle_bin_bottom">Koš jako poslední</string>
|
<string name="sort_recycle_bin_bottom">Koš jako poslední</string>
|
||||||
<string name="sort_title">Nadpis</string>
|
<string name="sort_title">Nadpis</string>
|
||||||
<string name="sort_username">Uživatelské jméno</string>
|
<string name="sort_username">Uživatelské jméno</string>
|
||||||
<string name="sort_creation_time">Vytvoření</string>
|
<string name="sort_creation_time">Založeno</string>
|
||||||
<string name="sort_last_modify_time">Změna</string>
|
<string name="sort_last_modify_time">Změněno</string>
|
||||||
<string name="sort_last_access_time">Přístup</string>
|
<string name="sort_last_access_time">Přístup</string>
|
||||||
<string name="warning">Varování</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_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ím heslem\?</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="warning_no_encryption_key">Pokračovat bez šifrovacího klíče\?</string>
|
||||||
<string name="encrypted_value_stored">Šifrované heslo uloženo</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>
|
<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="general">Obecné</string>
|
||||||
<string name="autofill">Automatické vyplnění</string>
|
<string name="autofill">Automatické vyplnění</string>
|
||||||
<string name="autofill_service_name">KeePassDX automatické vyplňování formulářů</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="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="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>
|
<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_title">Znaky hesla</string>
|
||||||
<string name="list_password_generator_options_summary">Nastavit povolené znaky pro generátor hesel</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">Schránka</string>
|
||||||
<string name="clipboard_notifications_title">Oznamování schránky</string>
|
<string name="clipboard_notifications_title">Oznámení 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_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="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">Zamknout</string>
|
||||||
<string name="lock_database_screen_off_title">Zámek obrazovky</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="lock_database_screen_off_summary">Při zhasnutí obrazovky uzamknout databázi</string>
|
||||||
<string name="advanced_unlock">Rozšířené odemknutí</string>
|
<string name="advanced_unlock">Rozšířené odemknutí</string>
|
||||||
<string name="biometric_unlock_enable_title">Biometrické odemčení</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_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_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 rozšířené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_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_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="file_name">Název souboru</string>
|
||||||
<string name="path">Cesta</string>
|
<string name="path">Cesta</string>
|
||||||
<string name="assign_master_key">Přiřadit hlavní klíč</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_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="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_title">Písmo kolonek</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_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_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_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>
|
<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_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_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_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_title">Přidejte záznamy do databáze</string>
|
||||||
<string name="education_new_node_summary">Položky pomáhají se správou vašich digitálních identit.
|
<string name="education_new_node_summary">Záznamy pomáhají se správou Vašich digitálních identit.
|
||||||
\n
|
\n
|
||||||
\nSkupiny (ekvivalent složek) organizují záznamy v databázi.</string>
|
\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_title">Hledat v záznamech</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_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 položku</string>
|
<string name="education_entry_edit_title">Upravit záznam</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_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_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_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_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_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_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í.
|
<string name="education_read_only_summary">Změnit režim otevírání pro dané sezení.
|
||||||
\n
|
\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>
|
\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_title">Zkopírovat kolonku</string>
|
||||||
<string name="education_field_copy_summary">Zkopírované kolonky lze vkládat kam chcete
|
<string name="education_field_copy_summary">Zkopírované kolonky lze vkládat podle libosti.
|
||||||
\n
|
\n
|
||||||
\nK vyplňování formulářů použijte svou oblíbenou metodu.</string>
|
\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_title">Uzamknout databázi</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_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_title">Řazení položek</string>
|
||||||
<string name="education_sort_summary">Vyberte řazení položek a skupin.</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_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="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, 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_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_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_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_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">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_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_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_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">Mnohé díky za vaše přispění.</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_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="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string>
|
||||||
<string name="download">Stáhnout</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="encryption_chacha20">ChaCha20</string>
|
||||||
<string name="kdf_AES">AES</string>
|
<string name="kdf_AES">AES</string>
|
||||||
<string name="style_choose_title">Vzhled aplikace</string>
|
<string name="style_choose_title">Vzhled aplikace</string>
|
||||||
@@ -304,16 +304,16 @@
|
|||||||
<string name="keyboard_name">Magikeyboard</string>
|
<string name="keyboard_name">Magikeyboard</string>
|
||||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||||
<string name="keyboard_setting_label">Magikeyboard nastavení</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_title">Časový limit</string>
|
||||||
<string name="keyboard_entry_timeout_summary">Doba uchování položky v Magikeyboardu</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_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_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_title">%1$s dostupné v Magikeyboardu</string>
|
||||||
<string name="keyboard_notification_entry_content_text">%1$s</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_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_appearance_category">Vzhled</string>
|
||||||
<string name="keyboard_theme_title">Vzhled klávesnice</string>
|
<string name="keyboard_theme_title">Vzhled klávesnice</string>
|
||||||
<string name="keyboard_keys_category">Klávesy</string>
|
<string name="keyboard_keys_category">Klávesy</string>
|
||||||
@@ -326,33 +326,33 @@
|
|||||||
<string name="clear_clipboard_notification_title">Vymazat při ukončení</string>
|
<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="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="recycle_bin">Koš</string>
|
||||||
<string name="keyboard_selection_entry_title">Výběr položky</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 pole položek</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_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="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_open_file">Otevřít soubor</string>
|
||||||
<string name="content_description_node_children">Potomci uzlu</string>
|
<string name="content_description_node_children">Podřazené prvky uzlu</string>
|
||||||
<string name="content_description_add_node">Přidej uzel</string>
|
<string name="content_description_add_node">Přidat uzel</string>
|
||||||
<string name="content_description_add_entry">Přidej záznam</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_add_group">Přidat skupinu</string>
|
||||||
<string name="content_description_file_information">Informace o souboru</string>
|
<string name="content_description_file_information">Informace o souboru</string>
|
||||||
<string name="content_description_password_checkbox">Checkbox hesla</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_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="content_description_entry_icon">Ikona záznamu</string>
|
||||||
<string name="entry_password_generator">Generátor hesel</string>
|
<string name="entry_password_generator">Generátor hesel</string>
|
||||||
<string name="content_description_password_length">Délka hesla</string>
|
<string name="content_description_password_length">Délka hesla</string>
|
||||||
<string name="entry_add_field">Přidej pole</string>
|
<string name="entry_add_field">Přidat pole</string>
|
||||||
<string name="content_description_remove_field">Odeber pole</string>
|
<string name="content_description_remove_field">Odebrat pole</string>
|
||||||
<string name="entry_UUID">UUID</string>
|
<string name="entry_UUID">UUID</string>
|
||||||
<string name="error_move_entry_here">Sem záznam přesunout nelze.</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="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_title">Ukázat 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_summary">Ukázat počet záznamů ve skupině</string>
|
||||||
<string name="content_description_background">Pozadí</string>
|
<string name="content_description_background">Pozadí</string>
|
||||||
<string name="content_description_update_from_list">Aktualizovat</string>
|
<string name="content_description_update_from_list">Aktualizovat</string>
|
||||||
<string name="content_description_keyboard_close_fields">Zavři kolonky</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 klíčem ze souboru.</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="menu_advanced_unlock_settings">Rozšířené odemknutí</string>
|
||||||
<string name="biometric">Biometrika</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_title">Automaticky otevřít pobídku</string>
|
||||||
@@ -372,7 +372,7 @@
|
|||||||
<string name="entry_otp">OTP</string>
|
<string name="entry_otp">OTP</string>
|
||||||
<string name="error_invalid_OTP">Neplatná OTP tajnost.</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_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_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_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>
|
<string name="error_otp_period">Interval musít být mezi %1$d a %2$d vteřinami.</string>
|
||||||
@@ -384,7 +384,7 @@
|
|||||||
<string name="contains_duplicate_uuid">Databáze obsahuje duplikátní UUID.</string>
|
<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="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="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="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="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_title">Komprese dat</string>
|
||||||
<string name="database_data_compression_summary">Komprese dat snižuje velikost databáze</string>
|
<string name="database_data_compression_summary">Komprese dat snižuje velikost databáze</string>
|
||||||
@@ -413,20 +413,20 @@
|
|||||||
<string name="recycle_bin_group_title">Skupina Koš</string>
|
<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_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="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_restore_entry_history">Obnovit historii</string>
|
||||||
<string name="menu_delete_entry_history">Smazat 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_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_attachment">Stáhnout %1$s</string>
|
||||||
<string name="download_initialization">Zahajuji…</string>
|
<string name="download_initialization">Zahajuji…</string>
|
||||||
<string name="download_progression">Probíhá: %1$d%%</string>
|
<string name="download_progression">Probíhá: %1$d%%</string>
|
||||||
<string name="download_finalization">Dokončuji…</string>
|
<string name="download_finalization">Dokončuji…</string>
|
||||||
<string name="download_complete">Kompletní!</string>
|
<string name="download_complete">Kompletní!</string>
|
||||||
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</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="contact">Kontakt</string>
|
||||||
<string name="contribution">Příspěvky</string>
|
<string name="contribution">Příspění</string>
|
||||||
<string name="feedback">Feedback</string>
|
<string name="feedback">Feedback</string>
|
||||||
<string name="auto_focus_search_title">Snadné hledání</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>
|
<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="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_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="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_title">Skrýt chybné 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_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="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>.
|
<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>
|
\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="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="entry_add_attachment">Přidat přílohu</string>
|
||||||
<string name="discard">Zahodit</string>
|
<string name="discard">Zavrhnout</string>
|
||||||
<string name="discard_changes">Zahodit změny\?</string>
|
<string name="discard_changes">Zavrhnout změny\?</string>
|
||||||
<string name="validate">Ověřit</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_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="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_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="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_summary">Zobrazí 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_title">Zobrazit tlačítko zámku</string>
|
||||||
<string name="autofill_preference_title">Nastavení samovyplnění</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="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>
|
<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="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="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="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_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_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_previous_database_credentials_title">Obrazovka ověřovacích údajů databáze</string>
|
||||||
<string name="keyboard_change">Přepnout klávesnici</string>
|
<string name="keyboard_change">Přepnout klávesnici</string>
|
||||||
<string name="upload_attachment">Nahrát %1$s</string>
|
<string name="upload_attachment">Nahrát %1$s</string>
|
||||||
<string name="content_description_credentials_information">Info o údajích</string>
|
<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).
|
<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
|
\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>
|
\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>
|
<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_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_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_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="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_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>
|
<string name="keyboard_previous_lock_title">Uzamknout databázi</string>
|
||||||
@@ -506,17 +506,17 @@
|
|||||||
<string name="notification">Oznámení</string>
|
<string name="notification">Oznámení</string>
|
||||||
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</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="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="registration_mode">Režim registrace</string>
|
||||||
<string name="save_mode">Režim ukládání</string>
|
<string name="save_mode">Režim ukládání</string>
|
||||||
<string name="search_mode">Režim vyhledává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="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="enter">Enter</string>
|
||||||
<string name="backspace">Backspace</string>
|
<string name="backspace">Backspace</string>
|
||||||
<string name="select_entry">Vybrat záznam</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="back_to_previous_keyboard">Zpět na předchozí klávesnici</string>
|
||||||
<string name="custom_fields">Vlastní položky</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="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_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_unlock_enable_title">Odemknutí heslem zařízení</string>
|
||||||
@@ -534,7 +534,7 @@
|
|||||||
<string name="open_advanced_unlock_prompt_unlock_database">Pro odemknutí databáze 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="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="education_advanced_unlock_title">Rozšířené odemknutí databáze</string>
|
||||||
<string name="advanced_unlock_timeout">Timeout rozšířeného odemknutí</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_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_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="temp_advanced_unlock_enable_title">Přechodné rozšířené odemknutí</string>
|
||||||
@@ -544,4 +544,6 @@
|
|||||||
<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="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="temp_advanced_unlock_timeout_title">Vypršení pokročilého odemknutí</string>
|
||||||
<string name="content">Obsah</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>
|
</resources>
|
||||||
@@ -555,4 +555,6 @@
|
|||||||
<string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</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_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</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>
|
</resources>
|
||||||
@@ -543,4 +543,13 @@
|
|||||||
<string name="content">Περιεχόμενα</string>
|
<string name="content">Περιεχόμενα</string>
|
||||||
<string name="kdf_Argon2id">Argon2id</string>
|
<string name="kdf_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</string>
|
<string name="kdf_Argon2d">Argon2d</string>
|
||||||
|
<string name="error_rebuild_list">Δεν είναι δυνατή η σωστή αναδημιουργία της λίστας.</string>
|
||||||
|
<string name="error_database_uri_null">Δεν είναι δυνατή η ανάκτηση του URI βάσης δεδομένων.</string>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Προστέθηκαν προτάσεις αυτόματης συμπλήρωσης.</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Απόπειρα εμφάνισης προτάσεων αυτόματης συμπλήρωσης απευθείας από ένα συμβατό πληκτρολόγιο</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Προτάσεις στην ίδια γραμμή</string>
|
||||||
|
<string name="warning_database_revoked">Πρόσβαση στο αρχείο που ανακλήθηκε από το διαχειριστή αρχείων, κλείστε τη βάση δεδομένων και ανοίξτε το ξανά από τη θέση του.</string>
|
||||||
|
<string name="warning_database_info_changed_options">Αντικαταστήστε τις εξωτερικές τροποποιήσεις αποθηκεύοντας τη βάση δεδομένων ή φορτώστε την ξανά με τις πιο πρόσφατες αλλαγές.</string>
|
||||||
|
<string name="warning_database_info_changed">Οι πληροφορίες που περιέχονται στο αρχείο της βάσης δεδομένων σας έχουν τροποποιηθεί εκτός της εφαρμογής.</string>
|
||||||
|
<string name="menu_reload_database">Επαναφόρτωση βάσης δεδομένων</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
<string name="contribute">Contribuir</string>
|
<string name="contribute">Contribuir</string>
|
||||||
<string name="encryption_chacha20">ChaCha20</string>
|
<string name="encryption_chacha20">ChaCha20</string>
|
||||||
<string name="kdf_AES">AES</string>
|
<string name="kdf_AES">AES</string>
|
||||||
<string name="style_choose_title">Tema de aplicación</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="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_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="icon_pack_choose_summary">Cambiar el paquete de iconos en la aplicación</string>
|
||||||
@@ -545,4 +545,6 @@
|
|||||||
<string name="keyboard_search_share_summary">Buscar automáticamente la información compartida para llenar el teclado</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_summary">Muestra el UUID vinculado a una entrada</string>
|
||||||
<string name="show_uuid_title">Mostrar UUID</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>
|
</resources>
|
||||||
@@ -552,4 +552,13 @@
|
|||||||
<string name="content">Contenu</string>
|
<string name="content">Contenu</string>
|
||||||
<string name="kdf_Argon2id">Argon2id</string>
|
<string name="kdf_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</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>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Suggestions de remplissage automatique ajoutées.</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Tente d\'afficher des suggestions de remplissage automatique directement à partir d\'un clavier compatible</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Suggestions en ligne</string>
|
||||||
|
<string name="warning_database_info_changed_options">Écraser les modifications externes en sauvegardant la base de données ou recharger-la avec les dernières modifications.</string>
|
||||||
|
<string name="warning_database_revoked">Accès au dossier révoqué par le gestionnaire de fichiers, fermer la base de données et la rouvrir à partir de son emplacement.</string>
|
||||||
|
<string name="warning_database_info_changed">Les informations contenues dans votre fichier de base de données ont été modifiées en dehors de l\'application.</string>
|
||||||
|
<string name="menu_reload_database">Recharger la base de données</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -527,4 +527,6 @@
|
|||||||
<string name="temp_advanced_unlock_enable_title">Privremeno napredno otključavanje</string>
|
<string name="temp_advanced_unlock_enable_title">Privremeno napredno otključavanje</string>
|
||||||
<string name="kdf_Argon2id">Argon2id</string>
|
<string name="kdf_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</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>
|
</resources>
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
<string name="autofill_service_name">Autocompletamento di KeePassDX</string>
|
<string name="autofill_service_name">Autocompletamento di KeePassDX</string>
|
||||||
<string name="autofill_sign_in_prompt">Accedi con KeePassDX</string>
|
<string name="autofill_sign_in_prompt">Accedi con KeePassDX</string>
|
||||||
<string name="set_autofill_service_title">Imposta servizio predefinito di autocompletamento</string>
|
<string name="set_autofill_service_title">Imposta servizio predefinito di autocompletamento</string>
|
||||||
<string name="autofill_explanation_summary">Attiva l\'autocompletamento per compilare velocemente i moduli in altre app</string>
|
<string name="autofill_explanation_summary">Attiva l\'autocompletamento per riempire velocemente i campi in altre app</string>
|
||||||
<string name="password_size_title">Dimensione password generata</string>
|
<string name="password_size_title">Dimensione password generata</string>
|
||||||
<string name="password_size_summary">Imposta la dimensione predefinita delle password generate</string>
|
<string name="password_size_summary">Imposta la dimensione predefinita delle password generate</string>
|
||||||
<string name="list_password_generator_options_title">Caratteri password</string>
|
<string name="list_password_generator_options_title">Caratteri password</string>
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
<string name="other">Altro</string>
|
<string name="other">Altro</string>
|
||||||
<string name="keyboard">Tastiera</string>
|
<string name="keyboard">Tastiera</string>
|
||||||
<string name="magic_keyboard_title">Magitastiera</string>
|
<string name="magic_keyboard_title">Magitastiera</string>
|
||||||
<string name="magic_keyboard_explanation_summary">Attiva una tastiera personale che popola le tue password e i campi di identità</string>
|
<string name="magic_keyboard_explanation_summary">Attiva una tastiera personale che inserisce le tue password e i campi di identità</string>
|
||||||
<string name="allow_no_password_title">Non consentire nessuna chiave principale</string>
|
<string name="allow_no_password_title">Non consentire nessuna chiave principale</string>
|
||||||
<string name="allow_no_password_summary">Permetti di toccare il pulsante \"Apri\" se non sono selezionate credenziali</string>
|
<string name="allow_no_password_summary">Permetti di toccare il pulsante \"Apri\" se non sono selezionate credenziali</string>
|
||||||
<string name="enable_read_only_title">Protetto da scrittura</string>
|
<string name="enable_read_only_title">Protetto da scrittura</string>
|
||||||
@@ -404,7 +404,7 @@
|
|||||||
<string name="remember_keyfile_locations_title">Ricorda posizione 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_summary">Ricorda la posizione dei database</string>
|
||||||
<string name="remember_database_locations_title">Ricorda posizione database</string>
|
<string name="remember_database_locations_title">Ricorda posizione database</string>
|
||||||
<string name="contains_duplicate_uuid_procedure">Per continuare, risolvi il problema generando nuovi UUID per i duplicati\?</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="error_create_database">Impossibile creare il file del database.</string>
|
||||||
<string name="entry_add_attachment">Aggiungi allegato</string>
|
<string name="entry_add_attachment">Aggiungi allegato</string>
|
||||||
<string name="discard">Scarta</string>
|
<string name="discard">Scarta</string>
|
||||||
@@ -428,7 +428,7 @@
|
|||||||
<string name="settings_database_recommend_changing_master_key_title">Rinnovo raccomandato</string>
|
<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_summary">Le voci scadute non sono mostrate</string>
|
||||||
<string name="hide_expired_entries_title">Nascondi le voci scadute</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_complete">Completato!</string>
|
||||||
<string name="download_finalization">Finalizzazione…</string>
|
<string name="download_finalization">Finalizzazione…</string>
|
||||||
<string name="download_progression">Avanzamento %1$d%%</string>
|
<string name="download_progression">Avanzamento %1$d%%</string>
|
||||||
@@ -451,7 +451,7 @@
|
|||||||
<string name="enable">Abilita</string>
|
<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_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_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_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="lock_database_show_button_title">Mostra il bottone di blocco</string>
|
||||||
<string name="autofill_preference_title">Impostazioni dell\'autocompletamento</string>
|
<string name="autofill_preference_title">Impostazioni dell\'autocompletamento</string>
|
||||||
@@ -546,4 +546,13 @@
|
|||||||
<string name="menu_keystore_remove_key">Elimina chiave di sblocco avanzato</string>
|
<string name="menu_keystore_remove_key">Elimina chiave di sblocco avanzato</string>
|
||||||
<string name="kdf_Argon2id">Argon2id</string>
|
<string name="kdf_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</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>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Suggerimento di riempimento aggiunto.</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Mostra i suggerimenti di riempimento campi in una tastiera compatibile</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Suggerimenti in linea</string>
|
||||||
|
<string name="warning_database_revoked">L\'accesso al file è stato revocato dal file manager, chiudi il database e riaprilo dalla sua posizione originale.</string>
|
||||||
|
<string name="warning_database_info_changed_options">Sovrascrivi le modifiche esterne salvano il database o ricaricalo con gli ultimi cambiamenti.</string>
|
||||||
|
<string name="warning_database_info_changed">I dati nel tuo database sono stati modificati al di fuori di questa app.</string>
|
||||||
|
<string name="menu_reload_database">Ricarica database</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -541,4 +541,7 @@
|
|||||||
<string name="temp_advanced_unlock_enable_title">一時的な高度なロック解除</string>
|
<string name="temp_advanced_unlock_enable_title">一時的な高度なロック解除</string>
|
||||||
<string name="advanced_unlock_tap_delete">タップして高度なロック解除用の鍵を削除する</string>
|
<string name="advanced_unlock_tap_delete">タップして高度なロック解除用の鍵を削除する</string>
|
||||||
<string name="content">コンテンツ</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>
|
</resources>
|
||||||
@@ -529,11 +529,27 @@
|
|||||||
<string name="menu_keystore_remove_key">Usuń zaawansowany klucz odblokowujący</string>
|
<string name="menu_keystore_remove_key">Usuń zaawansowany klucz odblokowujący</string>
|
||||||
<string name="education_advanced_unlock_summary">Połącz swoje hasło ze zeskanowanymi danymi biometrycznymi lub danymi logowania urządzenia, aby szybko odblokować bazę danych.</string>
|
<string name="education_advanced_unlock_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="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_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_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="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="advanced_unlock_tap_delete">Naciśnij, aby usunąć zaawansowane klucze odblokowujące</string>
|
||||||
<string name="content">Zawartość</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="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>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Dodano sugestie autouzupełniania.</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Sugestie wbudowane</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Spróbuj wyświetlić sugestie autouzupełniania bezpośrednio z kompatybilnej klawiatury</string>
|
||||||
|
<string name="temp_advanced_unlock_enable_title">Zaawansowane odblokowywanie tymczasowe</string>
|
||||||
|
<string name="advanced_unlock_prompt_not_initialized">Nie można zainicjować zaawansowanego monitu o odblokowanie.</string>
|
||||||
|
<string name="open_advanced_unlock_prompt_store_credential">Otwórz zaawansowany monit o odblokowanie, aby zapisać poświadczenia</string>
|
||||||
|
<string name="open_advanced_unlock_prompt_unlock_database">Otwórz zaawansowany monit o odblokowanie, aby odblokować bazę danych</string>
|
||||||
|
<string name="warning_database_revoked">Dostęp do pliku odwołany przez menedżera plików, zamknij bazę danych i otwórz ją ponownie z jej lokalizacji.</string>
|
||||||
|
<string name="warning_database_info_changed_options">Nadpisz zewnętrzne modyfikacje, zapisując bazę danych lub przeładuj ją z najnowszymi zmianami.</string>
|
||||||
|
<string name="warning_database_info_changed">Informacje zawarte w pliku bazy danych zostały zmodyfikowane poza aplikacją.</string>
|
||||||
|
<string name="menu_reload_database">Załaduj ponownie bazę danych</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -543,4 +543,13 @@
|
|||||||
<string name="content">Содержимое</string>
|
<string name="content">Содержимое</string>
|
||||||
<string name="kdf_Argon2id">Argon2ID</string>
|
<string name="kdf_Argon2id">Argon2ID</string>
|
||||||
<string name="kdf_Argon2d">Argon2D</string>
|
<string name="kdf_Argon2d">Argon2D</string>
|
||||||
|
<string name="error_database_uri_null">Невозможно получить URI базы.</string>
|
||||||
|
<string name="error_rebuild_list">Невозможно правильно перестроить список.</string>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Предложения автозаполнения добавлены.</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Показывать предложения автозаполнения непосредственно в совместимой клавиатуре</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Встроенные предложения</string>
|
||||||
|
<string name="warning_database_revoked">Доступ к файлу отозван файловым менеджером, закройте базу и снова откройте.</string>
|
||||||
|
<string name="warning_database_info_changed_options">Сохранить базу, перезаписав внешние изменения, или перезагрузить её с последними изменениями.</string>
|
||||||
|
<string name="warning_database_info_changed">Информация, содержащаяся в файле базы, была изменена вне этого приложения.</string>
|
||||||
|
<string name="menu_reload_database">Перезагрузить базу</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -527,4 +527,13 @@
|
|||||||
<string name="content">İçerik</string>
|
<string name="content">İçerik</string>
|
||||||
<string name="kdf_Argon2id">Argon2id</string>
|
<string name="kdf_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</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>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Otomatik doldurma önerileri eklendi.</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Doğrudan uyumlu bir klavyeden otomatik doldurma önerileri görüntülemeye çalış</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Satır içi öneriler</string>
|
||||||
|
<string name="warning_database_revoked">Dosyaya erişim dosya yöneticisi tarafından iptal edildi, veri tabanını kapatın ve bulunduğu yerden yeniden açın.</string>
|
||||||
|
<string name="warning_database_info_changed_options">Veri tabanını kaydederek veya en son değişikliklerle yeniden yükleyerek harici değişikliklerin üzerine yazın.</string>
|
||||||
|
<string name="warning_database_info_changed">Veri tabanı dosyanızda bulunan bilgiler, uygulamanın dışında değiştirildi.</string>
|
||||||
|
<string name="menu_reload_database">Veri tabanını yeniden yükle</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -543,4 +543,13 @@
|
|||||||
<string name="content">Вміст</string>
|
<string name="content">Вміст</string>
|
||||||
<string name="kdf_Argon2id">Argon2id</string>
|
<string name="kdf_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</string>
|
<string name="kdf_Argon2d">Argon2d</string>
|
||||||
|
<string name="error_rebuild_list">Не вдалося належним чином відновити список.</string>
|
||||||
|
<string name="error_database_uri_null">Неможливо отримати URI бази даних.</string>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Додано пропозиції автозаповнення.</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Спробувати показ пропозицій автозаповнення безпосередньо з сумісної клавіатури</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Вбудовані пропозиції</string>
|
||||||
|
<string name="warning_database_revoked">Доступ до файлу скасовано менеджером файлів, закрийте базу даних і знову відкрийте її з її розташування.</string>
|
||||||
|
<string name="warning_database_info_changed_options">Перезаписати зовнішні зміни, зберігши базу даних або перезавантажте її з найновішими змінами.</string>
|
||||||
|
<string name="warning_database_info_changed">Відомості, що містяться у файлі бази даних, змінено за межами застосунку.</string>
|
||||||
|
<string name="menu_reload_database">Перезавантажити базу даних</string>
|
||||||
</resources>
|
</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>
|
||||||
@@ -543,4 +543,13 @@
|
|||||||
<string name="content">内容</string>
|
<string name="content">内容</string>
|
||||||
<string name="kdf_Argon2id">Argon2id</string>
|
<string name="kdf_Argon2id">Argon2id</string>
|
||||||
<string name="kdf_Argon2d">Argon2d</string>
|
<string name="kdf_Argon2d">Argon2d</string>
|
||||||
|
<string name="error_rebuild_list">无法正确地重建列表。</string>
|
||||||
|
<string name="error_database_uri_null">无法检索数据库 URI 。</string>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">已添加自动填充建议。</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">尝试直接从兼容的键盘显示自动填充建议</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">内联建议</string>
|
||||||
|
<string name="warning_database_revoked">文件管理器撤销了对此文件的访问,关闭数据库并从其位置重新打开它。</string>
|
||||||
|
<string name="warning_database_info_changed_options">通过保存数据库或用最新的更改重新加载数据库来覆盖外部修改。</string>
|
||||||
|
<string name="warning_database_info_changed">数据库文件中包含的信息已在应用程序之外被修改。</string>
|
||||||
|
<string name="menu_reload_database">重新加载数据库</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -151,6 +151,8 @@
|
|||||||
<bool name="autofill_close_database_default" translatable="false">false</bool>
|
<bool name="autofill_close_database_default" translatable="false">false</bool>
|
||||||
<string name="autofill_auto_search_key" translatable="false">autofill_auto_search_key</string>
|
<string name="autofill_auto_search_key" translatable="false">autofill_auto_search_key</string>
|
||||||
<bool name="autofill_auto_search_default" translatable="false">true</bool>
|
<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>
|
<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>
|
<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>
|
<string name="autofill_ask_to_save_data_key" translatable="false">autofill_ask_to_save_data_key</string>
|
||||||
|
|||||||
@@ -185,6 +185,7 @@
|
|||||||
<string name="menu_hide_password">Hide password</string>
|
<string name="menu_hide_password">Hide password</string>
|
||||||
<string name="menu_lock">Lock database</string>
|
<string name="menu_lock">Lock database</string>
|
||||||
<string name="menu_save_database">Save 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_open">Open</string>
|
||||||
<string name="menu_search">Search</string>
|
<string name="menu_search">Search</string>
|
||||||
<string name="menu_showpass">Show password</string>
|
<string name="menu_showpass">Show password</string>
|
||||||
@@ -272,6 +273,9 @@
|
|||||||
<string name="warning_sure_remove_data">Remove this data anyway?</string>
|
<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">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_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="version_label">Version %1$s</string>
|
||||||
<string name="build_label">Build %1$s</string>
|
<string name="build_label">Build %1$s</string>
|
||||||
<string name="configure_biometric">No biometric or device credential is enrolled.</string>
|
<string name="configure_biometric">No biometric or device credential is enrolled.</string>
|
||||||
@@ -428,6 +432,8 @@
|
|||||||
<string name="autofill_close_database_summary">Close the database after an autofill selection</string>
|
<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_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_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_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_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>
|
<string name="autofill_ask_to_save_data_title">Ask to save data</string>
|
||||||
@@ -439,6 +445,7 @@
|
|||||||
<string name="autofill_block">Block autofill</string>
|
<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_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_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_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="allow_no_password_summary">Allows tapping the \"Open\" button if no credentials are selected</string>
|
||||||
<string name="delete_entered_password_title">Delete password</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:windowBackground">@android:color/transparent</item>
|
||||||
<item name="android:windowContentOverlay">@null</item>
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="android:windowIsFloating">true</item>
|
<item name="android:windowIsFloating">true</item>
|
||||||
<item name="android:backgroundDimEnabled">false</item>
|
<item name="android:backgroundDimEnabled">false</item>
|
||||||
</style>
|
</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"
|
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
tools:targetApi="p"
|
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
|
<compatibility-package
|
||||||
android:name="com.amazon.cloud9"
|
android:name="com.amazon.cloud9"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
|||||||
@@ -30,6 +30,11 @@
|
|||||||
android:title="@string/autofill_auto_search_title"
|
android:title="@string/autofill_auto_search_title"
|
||||||
android:summary="@string/autofill_auto_search_summary"
|
android:summary="@string/autofill_auto_search_summary"
|
||||||
android:defaultValue="@bool/autofill_auto_search_default"/>
|
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>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/save">
|
android:title="@string/save">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.4.10'
|
ext.kotlin_version = '1.4.21'
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
google()
|
google()
|
||||||
|
|||||||
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
|
||||||
2
fastlane/metadata/android/en-US/changelogs/54.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/54.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
* Try to fix autofill #852
|
||||||
|
* Fix database change dialog displayed too often #853
|
||||||
2
fastlane/metadata/android/en-US/changelogs/55.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/55.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
* Add Keyfile XML version 2 (fix hex) #844
|
||||||
|
* Fix hex Keyfile #861
|
||||||
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
|
||||||
2
fastlane/metadata/android/fr-FR/changelogs/54.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/54.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
* Tentative de résolution de l'autofill #852
|
||||||
|
* Correction du dialogue de changement de base de données affiché trop souvent #853
|
||||||
2
fastlane/metadata/android/fr-FR/changelogs/55.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/55.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
* Ajout de fichier de clé XML version 2 (correction hex) #844
|
||||||
|
* Correction de fichier de clé hex #861
|
||||||
@@ -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.
|
<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>.
|
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>
|
<b>Features</b>
|
||||||
- Create database files / entries and groups.
|
- 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.
|
<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>.
|
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>
|
<b>Fonctionnalités</b>
|
||||||
- Création de bases de données / entrées et groupes.
|
- Création de bases de données / entrées et groupes.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<b>複数の形式に対応する KeePass パスワード マネージャー</b>。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。
|
<b>複数の形式に対応する KeePass パスワード マネージャー</b>。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。
|
||||||
|
|
||||||
この pro バージョンは開発中です。購入することで<b>開発の加速</b>と<b>サービスの改善</b>を支援し、<b>広告なしのオープンソース ソフトウェア</b>の作成に貢献できます。
|
この pro バージョンは開発中です。購入することで<b>開発の加速</b>と<b>サービスの改善</b>を支援し、<b>広告なしのオープンソース ソフトウェア</b>の作成に貢献できます。
|
||||||
<i>現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。よく利用されるサイトやサービスの接続と同期を楽にする要素を統合することが予定されています。</i>
|
<i>現在、このアプリケーションの機能はテーマのロックが解除された free バージョンと同じです。一般的に使われている不自由なサイトやサービスに関連する要素を統合することを計画しています。</i>
|
||||||
|
|
||||||
<b>機能</b>
|
<b>機能</b>
|
||||||
- データベースファイル / エントリー・グループの作成
|
- データベースファイル / エントリー・グループの作成
|
||||||
|
|||||||
Reference in New Issue
Block a user