mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6b33d60c3 | ||
|
|
3fd06890d7 | ||
|
|
4af4ad7663 | ||
|
|
6ca8501e28 | ||
|
|
432b385f60 | ||
|
|
6cebdefa4a | ||
|
|
bc665eb83d | ||
|
|
cb187300fe | ||
|
|
f34e007ecd | ||
|
|
3b6ad080b4 | ||
|
|
9919e90ba5 | ||
|
|
f4af44925b | ||
|
|
4bb366b568 | ||
|
|
7e7ab4ce19 | ||
|
|
4d833d25ce | ||
|
|
a9c508ecd9 | ||
|
|
ef4dbb8fdb | ||
|
|
3fd13f3e3b | ||
|
|
319c9cad4b | ||
|
|
c12297c98d | ||
|
|
7c38361844 | ||
|
|
559554a975 | ||
|
|
7e2ffa2124 | ||
|
|
66dbac4bb2 | ||
|
|
8b6a843a85 | ||
|
|
976cff2751 | ||
|
|
f7c30fa8eb | ||
|
|
7757c8218b | ||
|
|
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 |
34
CHANGELOG
34
CHANGELOG
@@ -1,6 +1,36 @@
|
|||||||
KeePassDX(2.9.7)
|
KeePassDX(2.9.12)
|
||||||
|
* Fix OTP token type #863
|
||||||
|
* Fix auto open biometric prompt #862
|
||||||
|
* Fix back appearance setting #865
|
||||||
|
* Fix orientation change in settings #872
|
||||||
|
* Change memory unit to MiB #851
|
||||||
|
* Small changes #642
|
||||||
|
|
||||||
|
KeePassDX(2.9.11)
|
||||||
|
* Add Keyfile XML version 2 (fix hex) #844
|
||||||
|
* Fix hex Keyfile #861
|
||||||
|
|
||||||
|
KeePassDX(2.9.10)
|
||||||
|
* Try to fix autofill #852
|
||||||
|
* Fix database change dialog displayed too often #853
|
||||||
|
|
||||||
|
KeePassDX(2.9.9)
|
||||||
|
* Detect file changes and reload database #794
|
||||||
|
* Inline suggestions autofill with compatible keyboard (Android R) #827
|
||||||
|
* Add Keyfile XML version 2 #844
|
||||||
|
* Fix binaries of 64 bytes #835
|
||||||
|
* Special search in title fields #830
|
||||||
|
* Priority to OTP button in notifications #845
|
||||||
|
* Fix OTP generation for long secret key #848
|
||||||
|
* Fix small bugs #849
|
||||||
|
|
||||||
|
KeePassDX(2.9.8)
|
||||||
|
* Fix specific attachments with kdbx3.1 databases #828
|
||||||
|
* Fix small bugs
|
||||||
|
|
||||||
|
KeePassDX(2.9.7)
|
||||||
|
* Remove write permission since Android 10 #823
|
||||||
* Fix small bugs
|
* 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 = 56
|
||||||
versionName = "2.9.7"
|
versionName = "2.9.12"
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,10 +29,7 @@ import android.view.MotionEvent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.AdapterView
|
import android.widget.*
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.Spinner
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
@@ -57,6 +54,7 @@ class SetOTPDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
private var mOtpElement: OtpElement = OtpElement()
|
private var mOtpElement: OtpElement = OtpElement()
|
||||||
|
|
||||||
|
private var otpTypeMessage: TextView? = null
|
||||||
private var otpTypeSpinner: Spinner? = null
|
private var otpTypeSpinner: Spinner? = null
|
||||||
private var otpTokenTypeSpinner: Spinner? = null
|
private var otpTokenTypeSpinner: Spinner? = null
|
||||||
private var otpSecretContainer: TextInputLayout? = null
|
private var otpSecretContainer: TextInputLayout? = null
|
||||||
@@ -74,6 +72,8 @@ class SetOTPDialogFragment : DialogFragment() {
|
|||||||
private var totpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
private var totpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||||
private var hotpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
private var hotpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||||
private var otpAlgorithmAdapter: ArrayAdapter<TokenCalculator.HashAlgorithm>? = null
|
private var otpAlgorithmAdapter: ArrayAdapter<TokenCalculator.HashAlgorithm>? = null
|
||||||
|
private var mHotpTokenTypeArray: Array<OtpTokenType>? = null
|
||||||
|
private var mTotpTokenTypeArray: Array<OtpTokenType>? = null
|
||||||
|
|
||||||
private var mManualEvent = false
|
private var mManualEvent = false
|
||||||
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
|
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
|
||||||
@@ -134,6 +134,7 @@ class SetOTPDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
|
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
|
||||||
|
otpTypeMessage = root?.findViewById(R.id.setup_otp_type_message)
|
||||||
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
|
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
|
||||||
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
|
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
|
||||||
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
|
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
|
||||||
@@ -183,23 +184,23 @@ class SetOTPDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
// HOTP / TOTP Type selection
|
// HOTP / TOTP Type selection
|
||||||
val otpTypeArray = OtpType.values()
|
val otpTypeArray = OtpType.values()
|
||||||
otpTypeAdapter = ArrayAdapter<OtpType>(activity,
|
otpTypeAdapter = ArrayAdapter(activity,
|
||||||
android.R.layout.simple_spinner_item, otpTypeArray).apply {
|
android.R.layout.simple_spinner_item, otpTypeArray).apply {
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
}
|
}
|
||||||
otpTypeSpinner?.adapter = otpTypeAdapter
|
otpTypeSpinner?.adapter = otpTypeAdapter
|
||||||
|
|
||||||
// Otp Token type selection
|
// Otp Token type selection
|
||||||
val hotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
|
mHotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
|
||||||
hotpTokenTypeAdapter = ArrayAdapter(activity,
|
hotpTokenTypeAdapter = ArrayAdapter(activity,
|
||||||
android.R.layout.simple_spinner_item, hotpTokenTypeArray).apply {
|
android.R.layout.simple_spinner_item, mHotpTokenTypeArray!!).apply {
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
}
|
}
|
||||||
// Proprietary only on closed and full version
|
// Proprietary only on closed and full version
|
||||||
val totpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
|
mTotpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
|
||||||
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
|
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
|
||||||
totpTokenTypeAdapter = ArrayAdapter(activity,
|
totpTokenTypeAdapter = ArrayAdapter(activity,
|
||||||
android.R.layout.simple_spinner_item, totpTokenTypeArray).apply {
|
android.R.layout.simple_spinner_item, mTotpTokenTypeArray!!).apply {
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
}
|
}
|
||||||
otpTokenTypeAdapter = hotpTokenTypeAdapter
|
otpTokenTypeAdapter = hotpTokenTypeAdapter
|
||||||
@@ -207,7 +208,7 @@ class SetOTPDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
// OTP Algorithm
|
// OTP Algorithm
|
||||||
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
|
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
|
||||||
otpAlgorithmAdapter = ArrayAdapter<TokenCalculator.HashAlgorithm>(activity,
|
otpAlgorithmAdapter = ArrayAdapter(activity,
|
||||||
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
|
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
}
|
}
|
||||||
@@ -372,24 +373,40 @@ class SetOTPDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun upgradeTokenType() {
|
private fun upgradeTokenType() {
|
||||||
|
val tokenType = mOtpElement.tokenType
|
||||||
when (mOtpElement.type) {
|
when (mOtpElement.type) {
|
||||||
OtpType.HOTP -> {
|
OtpType.HOTP -> {
|
||||||
otpPeriodContainer?.visibility = View.GONE
|
otpPeriodContainer?.visibility = View.GONE
|
||||||
otpCounterContainer?.visibility = View.VISIBLE
|
otpCounterContainer?.visibility = View.VISIBLE
|
||||||
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
|
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
|
||||||
otpTokenTypeSpinner?.setSelection(OtpTokenType
|
mHotpTokenTypeArray?.let { otpTokenTypeArray ->
|
||||||
.getHotpTokenTypeValues().indexOf(mOtpElement.tokenType))
|
defineOtpTokenTypeSpinner(otpTokenTypeArray, tokenType, OtpTokenType.RFC4226)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OtpType.TOTP -> {
|
OtpType.TOTP -> {
|
||||||
otpPeriodContainer?.visibility = View.VISIBLE
|
otpPeriodContainer?.visibility = View.VISIBLE
|
||||||
otpCounterContainer?.visibility = View.GONE
|
otpCounterContainer?.visibility = View.GONE
|
||||||
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
|
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
|
||||||
otpTokenTypeSpinner?.setSelection(OtpTokenType
|
mTotpTokenTypeArray?.let { otpTokenTypeArray ->
|
||||||
.getTotpTokenTypeValues().indexOf(mOtpElement.tokenType))
|
defineOtpTokenTypeSpinner(otpTokenTypeArray, tokenType, OtpTokenType.RFC6238)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun defineOtpTokenTypeSpinner(otpTokenTypeArray: Array<OtpTokenType>,
|
||||||
|
tokenType: OtpTokenType,
|
||||||
|
defaultTokenType: OtpTokenType) {
|
||||||
|
val formTokenType = if (otpTokenTypeArray.contains(tokenType)) {
|
||||||
|
otpTypeMessage?.visibility = View.GONE
|
||||||
|
tokenType
|
||||||
|
} else {
|
||||||
|
otpTypeMessage?.visibility = View.VISIBLE
|
||||||
|
defaultTokenType
|
||||||
|
}
|
||||||
|
otpTokenTypeSpinner?.setSelection(otpTokenTypeArray.indexOf(formTokenType))
|
||||||
|
}
|
||||||
|
|
||||||
private fun upgradeParameters() {
|
private fun upgradeParameters() {
|
||||||
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
|
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
|
||||||
.indexOf(mOtpElement.algorithm))
|
.indexOf(mOtpElement.algorithm))
|
||||||
|
|||||||
@@ -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 -> {
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ abstract class LockingActivity : SpecialModeActivity() {
|
|||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
|
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
if (savedInstanceState != null
|
if (savedInstanceState != null
|
||||||
@@ -84,8 +87,6 @@ abstract class LockingActivity : SpecialModeActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mExitLock = false
|
mExitLock = false
|
||||||
|
|
||||||
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ class IOActionTask<T>(
|
|||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val asyncResult: Deferred<T?> = async {
|
val asyncResult: Deferred<T?> = async {
|
||||||
action.invoke()
|
try {
|
||||||
|
action.invoke()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
afterActionDatabaseListener?.invoke(asyncResult.await())
|
afterActionDatabaseListener?.invoke(asyncResult.await())
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
|||||||
disconnect()
|
disconnect()
|
||||||
}
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
connect(databaseUri)
|
|
||||||
this.mAutoOpenPrompt = autoOpenPrompt
|
this.mAutoOpenPrompt = autoOpenPrompt
|
||||||
|
connect(databaseUri)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
disconnect()
|
disconnect()
|
||||||
@@ -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.toCharArray())
|
||||||
|
} 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.toCharArray()))
|
||||||
|
.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).toCharArray())
|
||||||
|
} 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,53 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkDatabaseInfo() {
|
||||||
|
try {
|
||||||
|
mDatabase.fileUri?.let {
|
||||||
|
val previousDatabaseInfo = mSnapFileDatabaseInfo
|
||||||
|
val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||||
|
FileDatabaseInfo(applicationContext, it))
|
||||||
|
|
||||||
|
val oldDatabaseModification = previousDatabaseInfo?.lastModification
|
||||||
|
val newDatabaseModification = lastFileDatabaseInfo.lastModification
|
||||||
|
|
||||||
|
val conditionExists = previousDatabaseInfo != null
|
||||||
|
&& previousDatabaseInfo.exists != lastFileDatabaseInfo.exists
|
||||||
|
// To prevent dialog opening too often
|
||||||
|
val conditionLastModification = (oldDatabaseModification != null && newDatabaseModification != null
|
||||||
|
&& oldDatabaseModification < newDatabaseModification
|
||||||
|
&& mLastLocalSaveTime + 5000 < newDatabaseModification)
|
||||||
|
|
||||||
|
if (conditionExists || conditionLastModification) {
|
||||||
|
// Show the dialog only if it's real new info and not a delay after a save
|
||||||
|
Log.i(TAG, "Database file modified " +
|
||||||
|
"$previousDatabaseInfo != $lastFileDatabaseInfo ")
|
||||||
|
// Call listener to indicate a change in database info
|
||||||
|
if (previousDatabaseInfo != null) {
|
||||||
|
mDatabaseInfoListeners.forEach { listener ->
|
||||||
|
listener.onDatabaseInfoChanged(previousDatabaseInfo, lastFileDatabaseInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mSnapFileDatabaseInfo = lastFileDatabaseInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to check database info", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDatabaseInfo() {
|
||||||
|
try {
|
||||||
|
mDatabase.fileUri?.let {
|
||||||
|
mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||||
|
FileDatabaseInfo(applicationContext, it))
|
||||||
|
Log.i(TAG, "Database file saved $mSnapFileDatabaseInfo")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to check database info", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
super.onBind(intent)
|
super.onBind(intent)
|
||||||
return mActionTaskBinder
|
return mActionTaskBinder
|
||||||
@@ -138,6 +201,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 +256,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 +292,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 +328,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 +339,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 +425,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 +548,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 +864,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 +917,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
|
||||||
@@ -137,7 +138,7 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
|
|||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
fun setHexSecret(secret: String) {
|
fun setHexSecret(secret: String) {
|
||||||
if (secret.isNotEmpty())
|
if (secret.isNotEmpty())
|
||||||
otpModel.secret = Hex.decodeHex(secret)
|
otpModel.secret = Hex.decodeHex(secret.toCharArray())
|
||||||
else
|
else
|
||||||
throw IllegalArgumentException()
|
throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +386,13 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
}
|
}
|
||||||
if (styleEnabled) {
|
if (styleEnabled) {
|
||||||
Stylish.assignStyle(styleIdString)
|
Stylish.assignStyle(styleIdString)
|
||||||
activity.recreate()
|
// Relaunch the current activity to redraw theme
|
||||||
|
(activity as? SettingsActivity?)?.apply {
|
||||||
|
keepCurrentScreen()
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
styleEnabled
|
styleEnabled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
private var mEncryptionAlgorithmPref: DialogListExplanationPreference? = null
|
private var mEncryptionAlgorithmPref: DialogListExplanationPreference? = null
|
||||||
private var mKeyDerivationPref: DialogListExplanationPreference? = null
|
private var mKeyDerivationPref: DialogListExplanationPreference? = null
|
||||||
private var mRoundPref: InputKdfNumberPreference? = null
|
private var mRoundPref: InputKdfNumberPreference? = null
|
||||||
private var mMemoryPref: InputKdfNumberPreference? = null
|
private var mMemoryPref: InputKdfSizePreference? = null
|
||||||
private var mParallelismPref: InputKdfNumberPreference? = null
|
private var mParallelismPref: InputKdfNumberPreference? = null
|
||||||
|
|
||||||
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
@@ -231,7 +231,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Memory Usage
|
// Memory Usage
|
||||||
mMemoryPref = findPreference<InputKdfNumberPreference>(getString(R.string.memory_usage_key))?.apply {
|
mMemoryPref = findPreference<InputKdfSizePreference>(getString(R.string.memory_usage_key))?.apply {
|
||||||
summary = mDatabase.memoryUsage.toString()
|
summary = mDatabase.memoryUsage.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,6 +552,13 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
|
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.menu_reload_database -> {
|
||||||
|
settingActivity?.apply {
|
||||||
|
keepCurrentScreen()
|
||||||
|
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// Check the time lock before launching settings
|
// Check the time lock before launching settings
|
||||||
|
|||||||
@@ -35,9 +35,13 @@ abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY
|
APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getScreen(): Screen {
|
||||||
|
return Screen.values()[requireArguments().getInt(TAG_KEY)]
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
onCreateScreenPreference(
|
onCreateScreenPreference(
|
||||||
Screen.values()[requireArguments().getInt(TAG_KEY)],
|
getScreen(),
|
||||||
savedInstanceState,
|
savedInstanceState,
|
||||||
rootKey)
|
rootKey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,30 @@ 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)
|
||||||
|
}
|
||||||
|
// Eat state
|
||||||
|
intent.removeExtra(FRAGMENT_ARG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,25 +211,41 @@ 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)
|
||||||
hideOrShowLockButton(key)
|
hideOrShowLockButton(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen) {
|
/**
|
||||||
|
* To keep the current screen when activity is reloaded
|
||||||
|
*/
|
||||||
|
fun keepCurrentScreen() {
|
||||||
|
(supportFragmentManager.findFragmentByTag(TAG_NESTED) as? NestedSettingsFragment?)
|
||||||
|
?.getScreen()?.let { fragmentKey ->
|
||||||
|
intent.putExtra(FRAGMENT_ARG, fragmentKey.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
|
||||||
if (mTimeoutEnable)
|
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 +260,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)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import androidx.preference.DialogPreference
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
|
|
||||||
class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
|
open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
defStyleRes: Int = defStyleAttr)
|
defStyleRes: Int = defStyleAttr)
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.settings.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.utils.DataByte
|
||||||
|
|
||||||
|
class InputKdfSizePreference @JvmOverloads constructor(context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
|
defStyleRes: Int = defStyleAttr)
|
||||||
|
: InputKdfNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
override fun setSummary(summary: CharSequence) {
|
||||||
|
if (summary == UNKNOWN_VALUE_STRING) {
|
||||||
|
super.setSummary("")
|
||||||
|
} else {
|
||||||
|
var summaryString = summary
|
||||||
|
try {
|
||||||
|
val memorySize = summary.toString().toLong()
|
||||||
|
summaryString = if (memorySize > 0) {
|
||||||
|
// To convert bytes to mebibytes
|
||||||
|
DataByte(memorySize, DataByte.ByteFormat.BYTE)
|
||||||
|
.toBetterByteFormat().toString(context)
|
||||||
|
} else {
|
||||||
|
memorySize.toString()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
} finally {
|
||||||
|
super.setSummary(summaryString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ open class InputNumberPreference @JvmOverloads constructor(context: Context,
|
|||||||
|
|
||||||
override fun setSummary(summary: CharSequence) {
|
override fun setSummary(summary: CharSequence) {
|
||||||
if (summary == INFINITE_VALUE_STRING) {
|
if (summary == INFINITE_VALUE_STRING) {
|
||||||
super.setSummary("")
|
super.setSummary("∞")
|
||||||
} else {
|
} else {
|
||||||
super.setSummary(summary)
|
super.setSummary(summary)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.settings.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.utils.DataByte
|
||||||
|
|
||||||
|
open class InputSizePreference @JvmOverloads constructor(context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||||
|
defStyleRes: Int = defStyleAttr)
|
||||||
|
: InputNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
|
|
||||||
|
override fun setSummary(summary: CharSequence) {
|
||||||
|
var summaryString = summary
|
||||||
|
try {
|
||||||
|
val memorySize = summary.toString().toLong()
|
||||||
|
summaryString = if (memorySize >= 0) {
|
||||||
|
// To convert bytes to mebibytes
|
||||||
|
DataByte(memorySize, DataByte.ByteFormat.BYTE)
|
||||||
|
.toBetterByteFormat().toString(context)
|
||||||
|
} else {
|
||||||
|
memorySize.toString()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
} finally {
|
||||||
|
super.setSummary(summaryString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import com.kunzisoft.keepass.R
|
|||||||
abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
|
abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
|
||||||
|
|
||||||
private var inputTextView: EditText? = null
|
private var inputTextView: EditText? = null
|
||||||
|
private var textUnitView: TextView? = null
|
||||||
private var textExplanationView: TextView? = null
|
private var textExplanationView: TextView? = null
|
||||||
private var switchElementView: CompoundButton? = null
|
private var switchElementView: CompoundButton? = null
|
||||||
|
|
||||||
@@ -47,6 +48,14 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setInoutText(@StringRes inputTextId: Int) {
|
||||||
|
inputText = getString(inputTextId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showInputText(show: Boolean) {
|
||||||
|
inputTextView?.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
fun setInputTextError(error: CharSequence) {
|
fun setInputTextError(error: CharSequence) {
|
||||||
this.inputTextView?.error = error
|
this.inputTextView?.error = error
|
||||||
}
|
}
|
||||||
@@ -55,6 +64,24 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
|||||||
this.mOnInputTextEditorActionListener = onEditorActionListener
|
this.mOnInputTextEditorActionListener = onEditorActionListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var unitText: String?
|
||||||
|
get() = textUnitView?.text?.toString() ?: ""
|
||||||
|
set(unitText) {
|
||||||
|
textUnitView?.apply {
|
||||||
|
if (unitText != null && unitText.isNotEmpty()) {
|
||||||
|
text = unitText
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
text = ""
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUnitText(@StringRes unitTextId: Int) {
|
||||||
|
unitText = getString(unitTextId)
|
||||||
|
}
|
||||||
|
|
||||||
var explanationText: String?
|
var explanationText: String?
|
||||||
get() = textExplanationView?.text?.toString() ?: ""
|
get() = textExplanationView?.text?.toString() ?: ""
|
||||||
set(explanationText) {
|
set(explanationText) {
|
||||||
@@ -69,6 +96,10 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setExplanationText(@StringRes explanationTextId: Int) {
|
||||||
|
explanationText = getString(explanationTextId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBindDialogView(view: View) {
|
override fun onBindDialogView(view: View) {
|
||||||
super.onBindDialogView(view)
|
super.onBindDialogView(view)
|
||||||
|
|
||||||
@@ -93,6 +124,8 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
textUnitView = view.findViewById(R.id.input_text_unit)
|
||||||
|
textUnitView?.visibility = View.GONE
|
||||||
textExplanationView = view.findViewById(R.id.explanation_text)
|
textExplanationView = view.findViewById(R.id.explanation_text)
|
||||||
textExplanationView?.visibility = View.GONE
|
textExplanationView?.visibility = View.GONE
|
||||||
switchElementView = view.findViewById(R.id.switch_element)
|
switchElementView = view.findViewById(R.id.switch_element)
|
||||||
@@ -113,18 +146,6 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setInoutText(@StringRes inputTextId: Int) {
|
|
||||||
inputText = getString(inputTextId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showInputText(show: Boolean) {
|
|
||||||
inputTextView?.visibility = if (show) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setExplanationText(@StringRes explanationTextId: Int) {
|
|
||||||
explanationText = getString(explanationTextId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSwitchAction(onCheckedChange: ((isChecked: Boolean)-> Unit)?, defaultChecked: Boolean) {
|
fun setSwitchAction(onCheckedChange: ((isChecked: Boolean)-> Unit)?, defaultChecked: Boolean) {
|
||||||
switchElementView?.visibility = if (onCheckedChange == null) View.GONE else View.VISIBLE
|
switchElementView?.visibility = if (onCheckedChange == null) View.GONE else View.VISIBLE
|
||||||
switchElementView?.isChecked = defaultChecked
|
switchElementView?.isChecked = defaultChecked
|
||||||
|
|||||||
@@ -22,50 +22,76 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.utils.DataByte
|
||||||
|
|
||||||
class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||||
|
|
||||||
|
private var dataByte = DataByte(2L, DataByte.ByteFormat.MEBIBYTE)
|
||||||
|
|
||||||
override fun onBindDialogView(view: View) {
|
override fun onBindDialogView(view: View) {
|
||||||
super.onBindDialogView(view)
|
super.onBindDialogView(view)
|
||||||
|
|
||||||
setExplanationText(R.string.max_history_size_summary)
|
setExplanationText(R.string.max_history_size_summary)
|
||||||
database?.historyMaxSize?.let { maxItemsDatabase ->
|
database?.historyMaxSize?.let { maxItemsDatabase ->
|
||||||
inputText = maxItemsDatabase.toString()
|
dataByte = DataByte(maxItemsDatabase, DataByte.ByteFormat.BYTE)
|
||||||
|
.toBetterByteFormat()
|
||||||
|
inputText = dataByte.number.toString()
|
||||||
|
if (dataByte.number >= 0) {
|
||||||
|
setUnitText(dataByte.format.stringId)
|
||||||
|
} else {
|
||||||
|
unitText = null
|
||||||
|
}
|
||||||
|
|
||||||
setSwitchAction({ isChecked ->
|
setSwitchAction({ isChecked ->
|
||||||
inputText = if (!isChecked) {
|
if (!isChecked) {
|
||||||
INFINITE_MAX_HISTORY_SIZE.toString()
|
dataByte = INFINITE_MAX_HISTORY_SIZE_DATA_BYTE
|
||||||
} else
|
inputText = INFINITE_MAX_HISTORY_SIZE.toString()
|
||||||
DEFAULT_MAX_HISTORY_SIZE.toString()
|
unitText = null
|
||||||
|
} else {
|
||||||
|
dataByte = DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE
|
||||||
|
inputText = dataByte.number.toString()
|
||||||
|
setUnitText(dataByte.format.stringId)
|
||||||
|
}
|
||||||
showInputText(isChecked)
|
showInputText(isChecked)
|
||||||
}, maxItemsDatabase > INFINITE_MAX_HISTORY_SIZE)
|
}, maxItemsDatabase > INFINITE_MAX_HISTORY_SIZE)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDialogClosed(positiveResult: Boolean) {
|
override fun onDialogClosed(positiveResult: Boolean) {
|
||||||
if (positiveResult) {
|
if (positiveResult) {
|
||||||
database?.let { database ->
|
database?.let { database ->
|
||||||
var maxHistorySize: Long = try {
|
val maxHistorySize: Long = try {
|
||||||
inputText.toLong()
|
inputText.toLong()
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
DEFAULT_MAX_HISTORY_SIZE
|
DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE.toBytes()
|
||||||
}
|
}
|
||||||
if (maxHistorySize < INFINITE_MAX_HISTORY_SIZE) {
|
val numberOfBytes = if (maxHistorySize >= 0) {
|
||||||
maxHistorySize = INFINITE_MAX_HISTORY_SIZE
|
val dataByteConversion = DataByte(maxHistorySize, dataByte.format)
|
||||||
|
var bytes = dataByteConversion.toBytes()
|
||||||
|
if (bytes > Long.MAX_VALUE) {
|
||||||
|
bytes = Long.MAX_VALUE
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
} else {
|
||||||
|
INFINITE_MAX_HISTORY_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
val oldMaxHistorySize = database.historyMaxSize
|
val oldMaxHistorySize = database.historyMaxSize
|
||||||
database.historyMaxSize = maxHistorySize
|
database.historyMaxSize = numberOfBytes
|
||||||
|
|
||||||
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, maxHistorySize, mDatabaseAutoSaveEnable)
|
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, numberOfBytes, mDatabaseAutoSaveEnable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val DEFAULT_MAX_HISTORY_SIZE = 134217728L
|
|
||||||
const val INFINITE_MAX_HISTORY_SIZE = -1L
|
const val INFINITE_MAX_HISTORY_SIZE = -1L
|
||||||
|
|
||||||
|
private val INFINITE_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(INFINITE_MAX_HISTORY_SIZE, DataByte.ByteFormat.MEBIBYTE)
|
||||||
|
private val DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(6L, DataByte.ByteFormat.MEBIBYTE)
|
||||||
|
|
||||||
fun newInstance(key: String): MaxHistorySizePreferenceDialogFragmentCompat {
|
fun newInstance(key: String): MaxHistorySizePreferenceDialogFragmentCompat {
|
||||||
val fragment = MaxHistorySizePreferenceDialogFragmentCompat()
|
val fragment = MaxHistorySizePreferenceDialogFragmentCompat()
|
||||||
val bundle = Bundle(1)
|
val bundle = Bundle(1)
|
||||||
|
|||||||
@@ -22,33 +22,46 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.utils.DataByte
|
||||||
|
|
||||||
class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||||
|
|
||||||
|
private var dataByte = DataByte(MIN_MEMORY_USAGE, DataByte.ByteFormat.BYTE)
|
||||||
|
|
||||||
override fun onBindDialogView(view: View) {
|
override fun onBindDialogView(view: View) {
|
||||||
super.onBindDialogView(view)
|
super.onBindDialogView(view)
|
||||||
|
|
||||||
setExplanationText(R.string.memory_usage_explanation)
|
setExplanationText(R.string.memory_usage_explanation)
|
||||||
inputText = database?.memoryUsage?.toString()?: MIN_MEMORY_USAGE.toString()
|
|
||||||
|
val memoryBytes = database?.memoryUsage ?: MIN_MEMORY_USAGE
|
||||||
|
dataByte = DataByte(memoryBytes, DataByte.ByteFormat.BYTE)
|
||||||
|
.toBetterByteFormat()
|
||||||
|
inputText = dataByte.number.toString()
|
||||||
|
setUnitText(dataByte.format.stringId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDialogClosed(positiveResult: Boolean) {
|
override fun onDialogClosed(positiveResult: Boolean) {
|
||||||
if (positiveResult) {
|
if (positiveResult) {
|
||||||
database?.let { database ->
|
database?.let { database ->
|
||||||
var memoryUsage: Long = try {
|
var newMemoryUsage: Long = try {
|
||||||
inputText.toLong()
|
inputText.toLong()
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
MIN_MEMORY_USAGE
|
MIN_MEMORY_USAGE
|
||||||
}
|
}
|
||||||
if (memoryUsage < MIN_MEMORY_USAGE) {
|
if (newMemoryUsage < MIN_MEMORY_USAGE) {
|
||||||
memoryUsage = MIN_MEMORY_USAGE
|
newMemoryUsage = MIN_MEMORY_USAGE
|
||||||
|
}
|
||||||
|
// To transform in bytes
|
||||||
|
dataByte.number = newMemoryUsage
|
||||||
|
var numberOfBytes = dataByte.toBytes()
|
||||||
|
if (numberOfBytes > Long.MAX_VALUE) {
|
||||||
|
numberOfBytes = Long.MAX_VALUE
|
||||||
}
|
}
|
||||||
// TODO Max Memory
|
|
||||||
|
|
||||||
val oldMemoryUsage = database.memoryUsage
|
val oldMemoryUsage = database.memoryUsage
|
||||||
database.memoryUsage = memoryUsage
|
database.memoryUsage = numberOfBytes
|
||||||
|
|
||||||
mProgressDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(oldMemoryUsage, memoryUsage, mDatabaseAutoSaveEnable)
|
mProgressDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(oldMemoryUsage, numberOfBytes, mDatabaseAutoSaveEnable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
84
app/src/main/java/com/kunzisoft/keepass/utils/DataByte.kt
Normal file
84
app/src/main/java/com/kunzisoft/keepass/utils/DataByte.kt
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
|
class DataByte(var number: Long, var format: ByteFormat) {
|
||||||
|
|
||||||
|
fun toBetterByteFormat(): DataByte {
|
||||||
|
return when (this.format) {
|
||||||
|
ByteFormat.BYTE -> {
|
||||||
|
when {
|
||||||
|
//this.number % GIBIBYTES == 0L -> {
|
||||||
|
// DataByte((this.number / GIBIBYTES), ByteFormat.GIBIBYTE)
|
||||||
|
//}
|
||||||
|
this.number % MEBIBYTES == 0L -> {
|
||||||
|
DataByte((this.number / MEBIBYTES), ByteFormat.MEBIBYTE)
|
||||||
|
}
|
||||||
|
this.number % KIBIBYTES == 0L -> {
|
||||||
|
DataByte((this.number / KIBIBYTES), ByteFormat.KIBIBYTE)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
DataByte(this.number, ByteFormat.BYTE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
DataByte(toBytes(), ByteFormat.BYTE).toBetterByteFormat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes in current DataByte
|
||||||
|
*/
|
||||||
|
fun toBytes(): Long {
|
||||||
|
return when (this.format) {
|
||||||
|
ByteFormat.BYTE -> this.number
|
||||||
|
ByteFormat.KIBIBYTE -> this.number * KIBIBYTES
|
||||||
|
ByteFormat.MEBIBYTE -> this.number * MEBIBYTES
|
||||||
|
//ByteFormat.GIBIBYTE -> this.number * GIBIBYTES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$number ${format.name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toString(context: Context): String {
|
||||||
|
return "$number ${context.getString(format.stringId)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ByteFormat(@StringRes var stringId: Int) {
|
||||||
|
BYTE(R.string.unit_byte),
|
||||||
|
KIBIBYTE(R.string.unit_kibibyte),
|
||||||
|
MEBIBYTE(R.string.unit_mebibyte)
|
||||||
|
//GIBIBYTE(R.string.unit_gibibyte)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIBIBYTES = 1024L
|
||||||
|
const val MEBIBYTES = 1048576L
|
||||||
|
const val GIBIBYTES = 1073741824L
|
||||||
|
}
|
||||||
|
}
|
||||||
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>
|
||||||
@@ -157,8 +157,8 @@
|
|||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
android:importantForAutofill="yes"
|
android:importantForAutofill="yes"
|
||||||
android:autofillHints="password|"
|
android:autofillHints="password"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone|flagNoPersonalizedLearning"
|
||||||
android:maxLines="1"/>
|
android:maxLines="1"/>
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
android:inputType="textPassword|textMultiLine"
|
android:inputType="textPassword|textMultiLine"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
android:importantForAutofill="no"
|
android:importantForAutofill="no"
|
||||||
|
android:imeOptions="flagNoPersonalizedLearning"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:maxLines="10"
|
android:maxLines="10"
|
||||||
android:hint="@string/entry_password"/>
|
android:hint="@string/entry_password"/>
|
||||||
|
|||||||
@@ -29,6 +29,18 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:importantForAutofill="noExcludeDescendants"
|
android:importantForAutofill="noExcludeDescendants"
|
||||||
tools:targetApi="o">
|
tools:targetApi="o">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/setup_otp_type_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/card_view_margin"
|
||||||
|
android:layout_marginLeft="@dimen/card_view_margin"
|
||||||
|
android:layout_marginEnd="@dimen/card_view_margin"
|
||||||
|
android:layout_marginRight="@dimen/card_view_margin"
|
||||||
|
android:text="@string/error_otp_type"
|
||||||
|
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle"/>
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:id="@+id/card_view_otp_selection"
|
android:id="@+id/card_view_otp_selection"
|
||||||
android:layout_margin="4dp"
|
android:layout_margin="4dp"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine"
|
||||||
|
android:imeOptions="flagNoPersonalizedLearning"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
@@ -46,9 +47,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"/>
|
||||||
|
|||||||
@@ -46,14 +46,24 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/explanation_text"
|
app:layout_constraintTop_toBottomOf="@+id/explanation_text"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:minHeight="48dp"/>
|
android:minHeight="48dp"/>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatEditText
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
android:id="@+id/input_text"
|
android:id="@+id/input_text"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/switch_element"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:digits="0123456789"
|
android:digits="0123456789"
|
||||||
android:inputType="number"/>
|
android:inputType="number"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/input_text_unit"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/switch_element" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/input_text_unit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/input_text"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/input_text" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -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>
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
<string name="content_description_background">الخلفية</string>
|
<string name="content_description_background">الخلفية</string>
|
||||||
<string name="rounds">دورات التحويل</string>
|
<string name="rounds">دورات التحويل</string>
|
||||||
<string name="rounds_explanation">توفر الدورات الاضافية ضد هجوم توليد التركيبات ،لكنها تبطئ التحميل والحفظ.</string>
|
<string name="rounds_explanation">توفر الدورات الاضافية ضد هجوم توليد التركيبات ،لكنها تبطئ التحميل والحفظ.</string>
|
||||||
<string name="memory_usage_explanation">مقدار الذاكرة (بالبايت) لاستخدامها في دالة اشتقاق المفتاح.</string>
|
<string name="memory_usage_explanation">مقدار الذاكرة لاستخدامها في دالة اشتقاق المفتاح.</string>
|
||||||
<string name="parallelism_explanation">درجة التوازي (عدد العمليات) لدالة اشتقاق المفتاح.</string>
|
<string name="parallelism_explanation">درجة التوازي (عدد العمليات) لدالة اشتقاق المفتاح.</string>
|
||||||
<string name="sort_groups_before">مجموعات قبل</string>
|
<string name="sort_groups_before">مجموعات قبل</string>
|
||||||
<string name="selection_mode">نمط التحديد</string>
|
<string name="selection_mode">نمط التحديد</string>
|
||||||
@@ -404,7 +404,7 @@
|
|||||||
<string name="settings_database_force_changing_master_key_summary">يطالبك بتغيير المفتاح الرئيسي (بالأيام)</string>
|
<string name="settings_database_force_changing_master_key_summary">يطالبك بتغيير المفتاح الرئيسي (بالأيام)</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">اقترح تجديد المفتاح الرئيسي (بالأيام)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">اقترح تجديد المفتاح الرئيسي (بالأيام)</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">اقترح التجديد</string>
|
<string name="settings_database_recommend_changing_master_key_title">اقترح التجديد</string>
|
||||||
<string name="max_history_size_summary">حجم التأريخ ( بالبايت) لكل مدخل</string>
|
<string name="max_history_size_summary">حجم التأريخ لكل مدخل</string>
|
||||||
<string name="max_history_size_title">الحجم الأقصى</string>
|
<string name="max_history_size_title">الحجم الأقصى</string>
|
||||||
<string name="max_history_items_summary">عدد عناصر التأريخ لكل مدخل</string>
|
<string name="max_history_items_summary">عدد عناصر التأريخ لكل مدخل</string>
|
||||||
<string name="education_generate_password_title">أنشئ كلمة سر قوية</string>
|
<string name="education_generate_password_title">أنشئ كلمة سر قوية</string>
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
<string name="settings_database_force_changing_master_key_title">Forsiraj obnovu</string>
|
<string name="settings_database_force_changing_master_key_title">Forsiraj obnovu</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">Preporučiti promenu glavnog ključa (u danima)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">Preporučiti promenu glavnog ključa (u danima)</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Preporučiti obnavljanje</string>
|
<string name="settings_database_recommend_changing_master_key_title">Preporučiti obnavljanje</string>
|
||||||
<string name="max_history_size_summary">Ograniči veličinu istorije (u bajtovima) po unosu</string>
|
<string name="max_history_size_summary">Ograniči veličinu istorije po unosu</string>
|
||||||
<string name="max_history_size_title">Maksimalna veličina</string>
|
<string name="max_history_size_title">Maksimalna veličina</string>
|
||||||
<string name="max_history_items_summary">Ograniči broja stavki istorije po unosu</string>
|
<string name="max_history_items_summary">Ograniči broja stavki istorije po unosu</string>
|
||||||
<string name="max_history_items_title">Maksimalan broj</string>
|
<string name="max_history_items_title">Maksimalan broj</string>
|
||||||
|
|||||||
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>
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
<string name="command_execution">S\'executa l\'ordre…</string>
|
<string name="command_execution">S\'executa l\'ordre…</string>
|
||||||
<string name="parallelism_explanation">Grau de paral·lelisme (és a dir, nombre de fils) fets servir per la funció de derivació de la clau.</string>
|
<string name="parallelism_explanation">Grau de paral·lelisme (és a dir, nombre de fils) fets servir per la funció de derivació de la clau.</string>
|
||||||
<string name="parallelism">Paral·lelisme</string>
|
<string name="parallelism">Paral·lelisme</string>
|
||||||
<string name="memory_usage_explanation">Quantitat de memòria (en bytes) usada per la funció de derivació de la clau.</string>
|
<string name="memory_usage_explanation">Quantitat de memòria usada per la funció de derivació de la clau.</string>
|
||||||
<string name="memory_usage">Ús de la memòria</string>
|
<string name="memory_usage">Ús de la memòria</string>
|
||||||
<string name="hide_broken_locations_summary">Amaga els enllaços trencats en la llista de bases de dades recents</string>
|
<string name="hide_broken_locations_summary">Amaga els enllaços trencats en la llista de bases de dades recents</string>
|
||||||
<string name="show_recent_files_summary">Mostra la ubicació de les bases de dades recents</string>
|
<string name="show_recent_files_summary">Mostra la ubicació de les bases de dades recents</string>
|
||||||
|
|||||||
@@ -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 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,14 +384,14 @@
|
|||||||
<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>
|
||||||
<string name="max_history_items_title">Maximální počet</string>
|
<string name="max_history_items_title">Maximální počet</string>
|
||||||
<string name="max_history_items_summary">Omezit počet položek v historii záznamu</string>
|
<string name="max_history_items_summary">Omezit počet položek v historii záznamu</string>
|
||||||
<string name="max_history_size_title">Maximální velikost</string>
|
<string name="max_history_size_title">Maximální velikost</string>
|
||||||
<string name="max_history_size_summary">Omezit velikost historie na záznam (v bajtech)</string>
|
<string name="max_history_size_summary">Omezit velikost historie na záznam</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Doporučit změnu</string>
|
<string name="settings_database_recommend_changing_master_key_title">Doporučit změnu</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">Dporučit změnu hlavního klíče (dny)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">Dporučit změnu hlavního klíče (dny)</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">Vynutit změnu</string>
|
<string name="settings_database_force_changing_master_key_title">Vynutit změnu</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,13 @@
|
|||||||
<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>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Návrhy samovyplnění přidány.</string>
|
||||||
|
<string name="autofill_inline_suggestions_title">Návrhy inline</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Pokusí ze zobrazit návrhy samovyplnění přímo z kompatibilní klávesnice</string>
|
||||||
|
<string name="warning_database_revoked">Přístup k souboru odebrán správcem souborů, uzavřete databázi a nově ji otevřete z jejího adresáře.</string>
|
||||||
|
<string name="menu_reload_database">Databázi nově načíst</string>
|
||||||
|
<string name="warning_database_info_changed_options">Přepsat externí změny uložením databáze nebo databázi včetně posledních změn nově načíst.</string>
|
||||||
|
<string name="warning_database_info_changed">Informace obsažená ve Vašem databázovém souboru by změněna mimo aplikaci.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
<string name="encryption_explanation">Databasekrypteringsalgoritme anvendt for alle data.</string>
|
<string name="encryption_explanation">Databasekrypteringsalgoritme anvendt for alle data.</string>
|
||||||
<string name="kdf_explanation">For at generere nøglen til krypteringsalgoritmen, omdannes hovednøglen ved hjælp af en tilfældigt saltet nøgleafledningsfunktion.</string>
|
<string name="kdf_explanation">For at generere nøglen til krypteringsalgoritmen, omdannes hovednøglen ved hjælp af en tilfældigt saltet nøgleafledningsfunktion.</string>
|
||||||
<string name="memory_usage">Hukommelsesforbrug</string>
|
<string name="memory_usage">Hukommelsesforbrug</string>
|
||||||
<string name="memory_usage_explanation">Hukommelse (i bytes), som anvendes af nøgleafledningsfunktion.</string>
|
<string name="memory_usage_explanation">Hukommelse, som anvendes af nøgleafledningsfunktion.</string>
|
||||||
<string name="parallelism">Parallelitet</string>
|
<string name="parallelism">Parallelitet</string>
|
||||||
<string name="parallelism_explanation">Grad af parallelitet (dvs. antallet af tråde), som anvendes af nøgleafledningsfunktion.</string>
|
<string name="parallelism_explanation">Grad af parallelitet (dvs. antallet af tråde), som anvendes af nøgleafledningsfunktion.</string>
|
||||||
<string name="sort_menu">Sorter</string>
|
<string name="sort_menu">Sorter</string>
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
<string name="max_history_items_title">Max. antal</string>
|
<string name="max_history_items_title">Max. antal</string>
|
||||||
<string name="max_history_items_summary">Begræns antallet af historikposter pr. indtastning</string>
|
<string name="max_history_items_summary">Begræns antallet af historikposter pr. indtastning</string>
|
||||||
<string name="max_history_size_title">Max. størrelse</string>
|
<string name="max_history_size_title">Max. størrelse</string>
|
||||||
<string name="max_history_size_summary">Begræns historikstørrelsen (i bytes) pr. post</string>
|
<string name="max_history_size_summary">Begræns historikstørrelsen pr. post</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Anbefalet fornyelse</string>
|
<string name="settings_database_recommend_changing_master_key_title">Anbefalet fornyelse</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">Anbefal ændring af hovednøglen (dage)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">Anbefal ændring af hovednøglen (dage)</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">Gennemtving fornyelse</string>
|
<string name="settings_database_force_changing_master_key_title">Gennemtving fornyelse</string>
|
||||||
|
|||||||
@@ -199,7 +199,7 @@
|
|||||||
<string name="encryption_explanation">Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet.</string>
|
<string name="encryption_explanation">Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet.</string>
|
||||||
<string name="kdf_explanation">Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird.</string>
|
<string name="kdf_explanation">Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird.</string>
|
||||||
<string name="memory_usage">Speichernutzung</string>
|
<string name="memory_usage">Speichernutzung</string>
|
||||||
<string name="memory_usage_explanation">Größe des Speichers (in Bytes) der für die Schlüsselableitung genutzt wird.</string>
|
<string name="memory_usage_explanation">Größe des Speichers der für die Schlüsselableitung genutzt wird.</string>
|
||||||
<string name="parallelism">Parallelismus</string>
|
<string name="parallelism">Parallelismus</string>
|
||||||
<string name="parallelism_explanation">Grad des Parallelismus (d. h. Anzahl der Threads), der für die Schlüsselableitung genutzt wird.</string>
|
<string name="parallelism_explanation">Grad des Parallelismus (d. h. Anzahl der Threads), der für die Schlüsselableitung genutzt wird.</string>
|
||||||
<string name="sort_menu">Sortieren</string>
|
<string name="sort_menu">Sortieren</string>
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
<string name="max_history_items_title">Maximale Anzahl</string>
|
<string name="max_history_items_title">Maximale Anzahl</string>
|
||||||
<string name="max_history_items_summary">Anzahl der Verlaufseinträge pro Eintrag begrenzen</string>
|
<string name="max_history_items_summary">Anzahl der Verlaufseinträge pro Eintrag begrenzen</string>
|
||||||
<string name="max_history_size_title">Maximale Größe</string>
|
<string name="max_history_size_title">Maximale Größe</string>
|
||||||
<string name="max_history_size_summary">Verlaufsumfang (in Bytes) pro Eintrag begrenzen</string>
|
<string name="max_history_size_summary">Verlaufsumfang pro Eintrag begrenzen</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Erneuerung empfehlen</string>
|
<string name="settings_database_recommend_changing_master_key_title">Erneuerung empfehlen</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">(Nach Tagen) Änderung des Hauptschlüssels empfehlen</string>
|
<string name="settings_database_recommend_changing_master_key_summary">(Nach Tagen) Änderung des Hauptschlüssels empfehlen</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">Erneuerung erzwingen</string>
|
<string name="settings_database_force_changing_master_key_title">Erneuerung erzwingen</string>
|
||||||
@@ -555,4 +555,7 @@
|
|||||||
<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>
|
||||||
</resources>
|
<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>
|
||||||
|
<string name="menu_reload_database">Datenbank neu laden</string>
|
||||||
|
</resources>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
<string name="encryption_explanation">Αλγόριθμος κρυπτογράφησης βάσης δεδομένων που χρησιμοποιείται για όλα τα δεδομένα.</string>
|
<string name="encryption_explanation">Αλγόριθμος κρυπτογράφησης βάσης δεδομένων που χρησιμοποιείται για όλα τα δεδομένα.</string>
|
||||||
<string name="kdf_explanation">Για να δημιουργηθεί το κλειδί για τον αλγόριθμο κρυπτογράφησης, το κύριο κλειδί μετασχηματίζεται χρησιμοποιώντας μια τυχαία αλατισμένη λειτουργία εξαγωγής κλειδιών.</string>
|
<string name="kdf_explanation">Για να δημιουργηθεί το κλειδί για τον αλγόριθμο κρυπτογράφησης, το κύριο κλειδί μετασχηματίζεται χρησιμοποιώντας μια τυχαία αλατισμένη λειτουργία εξαγωγής κλειδιών.</string>
|
||||||
<string name="memory_usage">Χρήση μνήμης</string>
|
<string name="memory_usage">Χρήση μνήμης</string>
|
||||||
<string name="memory_usage_explanation">Ποσότητα μνήμης (σε bytes) που θα χρησιμοποιηθεί από τη λειτουργία εξαγωγής κλειδιών.</string>
|
<string name="memory_usage_explanation">Ποσότητα μνήμης που θα χρησιμοποιηθεί από τη λειτουργία εξαγωγής κλειδιών.</string>
|
||||||
<string name="parallelism">Παραλληλισμός</string>
|
<string name="parallelism">Παραλληλισμός</string>
|
||||||
<string name="parallelism_explanation">Βαθμός παραλληλισμού (δηλ. Αριθμός νημάτων) που χρησιμοποιείται από τη συνάρτηση εξαγωγής κλειδιών.</string>
|
<string name="parallelism_explanation">Βαθμός παραλληλισμού (δηλ. Αριθμός νημάτων) που χρησιμοποιείται από τη συνάρτηση εξαγωγής κλειδιών.</string>
|
||||||
<string name="sort_menu">Ταξινόμηση</string>
|
<string name="sort_menu">Ταξινόμηση</string>
|
||||||
@@ -392,7 +392,7 @@
|
|||||||
<string name="max_history_items_title">Μέγιστος αριθμός</string>
|
<string name="max_history_items_title">Μέγιστος αριθμός</string>
|
||||||
<string name="max_history_items_summary">Περιορίστε τον αριθμό των στοιχείων ιστορικού ανά καταχώριση</string>
|
<string name="max_history_items_summary">Περιορίστε τον αριθμό των στοιχείων ιστορικού ανά καταχώριση</string>
|
||||||
<string name="max_history_size_title">Μέγιστο μέγεθος</string>
|
<string name="max_history_size_title">Μέγιστο μέγεθος</string>
|
||||||
<string name="max_history_size_summary">Περιορίστε το μέγεθος ιστορικού (σε bytes) ανά καταχώριση</string>
|
<string name="max_history_size_summary">Περιορίστε το μέγεθος ιστορικού ανά καταχώριση</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Συστήστε ανανέωση</string>
|
<string name="settings_database_recommend_changing_master_key_title">Συστήστε ανανέωση</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">Προτεινόμενη αλλαγή του κύριου κλειδιού (ημέρες)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">Προτεινόμενη αλλαγή του κύριου κλειδιού (ημέρες)</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">Εξαναγκαστική ανανέωση</string>
|
<string name="settings_database_force_changing_master_key_title">Εξαναγκαστική ανανέωση</string>
|
||||||
@@ -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>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
<string name="encryption_explanation">Algoritmo de cifrado utilizado para todos los datos.</string>
|
<string name="encryption_explanation">Algoritmo de cifrado utilizado para todos los datos.</string>
|
||||||
<string name="kdf_explanation">Para generar la clave del algoritmo de cifrado, la clave maestra se transforma mediante una función de derivación de claves con una sal aleatoria.</string>
|
<string name="kdf_explanation">Para generar la clave del algoritmo de cifrado, la clave maestra se transforma mediante una función de derivación de claves con una sal aleatoria.</string>
|
||||||
<string name="memory_usage">Uso de memoria</string>
|
<string name="memory_usage">Uso de memoria</string>
|
||||||
<string name="memory_usage_explanation">Cantidad de memoria (en bytes) que usará la función de derivación de clave.</string>
|
<string name="memory_usage_explanation">Cantidad de memoria que usará la función de derivación de clave.</string>
|
||||||
<string name="parallelism">Paralelismo</string>
|
<string name="parallelism">Paralelismo</string>
|
||||||
<string name="parallelism_explanation">Grado de paralelismo (p. ej. número de hilos) usados por la función de derivación de clave.</string>
|
<string name="parallelism_explanation">Grado de paralelismo (p. ej. número de hilos) usados por la función de derivación de clave.</string>
|
||||||
<string name="sort_menu">Ordenar</string>
|
<string name="sort_menu">Ordenar</string>
|
||||||
@@ -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>
|
||||||
@@ -498,7 +498,7 @@
|
|||||||
<string name="error_field_name_already_exists">El nombre del campo ya existe.</string>
|
<string name="error_field_name_already_exists">El nombre del campo ya existe.</string>
|
||||||
<string name="error_registration_read_only">Guardar un nuevo elemento no está permitido en una base de datos de sólo lectura</string>
|
<string name="error_registration_read_only">Guardar un nuevo elemento no está permitido en una base de datos de sólo lectura</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Recomendar la renovación</string>
|
<string name="settings_database_recommend_changing_master_key_title">Recomendar la renovación</string>
|
||||||
<string name="max_history_size_summary">Limitar el tamaño del historial (en bytes) por entrada</string>
|
<string name="max_history_size_summary">Limitar el tamaño del historial por entrada</string>
|
||||||
<string name="max_history_items_summary">Limitar el número de elementos del historial por entrada</string>
|
<string name="max_history_items_summary">Limitar el número de elementos del historial por entrada</string>
|
||||||
<string name="max_history_items_title">Número máximo</string>
|
<string name="max_history_items_title">Número máximo</string>
|
||||||
<string name="device_keyboard_setting_title">Configuración del teclado del dispositivo</string>
|
<string name="device_keyboard_setting_title">Configuración del teclado del dispositivo</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>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<string name="saving_database">در حال ذخیره پایگاه داده</string>
|
<string name="saving_database">در حال ذخیره پایگاه داده</string>
|
||||||
<string name="parallelism_explanation">درجه موازی سازی (یعنی تعداد موضوعات) که توسط عملکرد مشتق کلیدی استفاده می شود.</string>
|
<string name="parallelism_explanation">درجه موازی سازی (یعنی تعداد موضوعات) که توسط عملکرد مشتق کلیدی استفاده می شود.</string>
|
||||||
<string name="parallelism">موازی کاری</string>
|
<string name="parallelism">موازی کاری</string>
|
||||||
<string name="memory_usage_explanation">مقدار حافظه (در بایت) که توسط تابع مشتق کلید مورد استفاده قرار گیرد.</string>
|
<string name="memory_usage_explanation">مقدار حافظه که توسط تابع مشتق کلید مورد استفاده قرار گیرد.</string>
|
||||||
<string name="memory_usage">استفاده از حافظه</string>
|
<string name="memory_usage">استفاده از حافظه</string>
|
||||||
<string name="rounds_explanation">دورهای رمزگذاری اضافی محافظت بالاتری در برابر حملات نیروی وحشی ایجاد می کنند ، اما در واقع می توانند سرعت و بارگذاری را کاهش دهند.</string>
|
<string name="rounds_explanation">دورهای رمزگذاری اضافی محافظت بالاتری در برابر حملات نیروی وحشی ایجاد می کنند ، اما در واقع می توانند سرعت و بارگذاری را کاهش دهند.</string>
|
||||||
<string name="rounds">دور تحول</string>
|
<string name="rounds">دور تحول</string>
|
||||||
|
|||||||
@@ -249,7 +249,7 @@
|
|||||||
<string name="list_password_generator_options_title">Salasanamerkit</string>
|
<string name="list_password_generator_options_title">Salasanamerkit</string>
|
||||||
<string name="password_size_summary">Asettaa oletuspituuden generoiduille salasanoille</string>
|
<string name="password_size_summary">Asettaa oletuspituuden generoiduille salasanoille</string>
|
||||||
<string name="parallelism">Rinnakkaisuus</string>
|
<string name="parallelism">Rinnakkaisuus</string>
|
||||||
<string name="memory_usage_explanation">Avaimen johtamisfunktion käyttämän muistin (tavuina) määrä.</string>
|
<string name="memory_usage_explanation">Avaimen johtamisfunktion käyttämän muistin määrä.</string>
|
||||||
<string name="memory_usage">Muistin käyttö</string>
|
<string name="memory_usage">Muistin käyttö</string>
|
||||||
<string name="kdf_explanation">Pääavain muunnetaan käyttäen satunnaista suolattua avaimen johtamisfunktiota, jotta salausalgoritmin avain voidaan generoida.</string>
|
<string name="kdf_explanation">Pääavain muunnetaan käyttäen satunnaista suolattua avaimen johtamisfunktiota, jotta salausalgoritmin avain voidaan generoida.</string>
|
||||||
<string name="encryption_explanation">Salasanatietokannan salausalgoritmi, jota käytetään kaikelle datalle.</string>
|
<string name="encryption_explanation">Salasanatietokannan salausalgoritmi, jota käytetään kaikelle datalle.</string>
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
<string name="rounds">Tours de transformation</string>
|
<string name="rounds">Tours de transformation</string>
|
||||||
<string name="rounds_explanation">Des tours de chiffrement supplémentaires fournissent une protection plus élevée contre les attaques par force brute, mais cela peut considérablement ralentir les opérations de chargement et d’enregistrement.</string>
|
<string name="rounds_explanation">Des tours de chiffrement supplémentaires fournissent une protection plus élevée contre les attaques par force brute, mais cela peut considérablement ralentir les opérations de chargement et d’enregistrement.</string>
|
||||||
<string name="memory_usage">Utilisation de la mémoire</string>
|
<string name="memory_usage">Utilisation de la mémoire</string>
|
||||||
<string name="memory_usage_explanation">Quantité de mémoire (en octets) à utiliser par la fonction de dérivation de clé.</string>
|
<string name="memory_usage_explanation">Quantité de mémoire à utiliser par la fonction de dérivation de clé.</string>
|
||||||
<string name="parallelism">Parallélisme</string>
|
<string name="parallelism">Parallélisme</string>
|
||||||
<string name="parallelism_explanation">Degré de parallélisme (nombre de fils d’exécution) utilisé par la fonction de dérivation de clé.</string>
|
<string name="parallelism_explanation">Degré de parallélisme (nombre de fils d’exécution) utilisé par la fonction de dérivation de clé.</string>
|
||||||
<string name="saving_database">Enregistrement de la base de données…</string>
|
<string name="saving_database">Enregistrement de la base de données…</string>
|
||||||
@@ -408,7 +408,7 @@
|
|||||||
<string name="max_history_items_title">Nombre maximum</string>
|
<string name="max_history_items_title">Nombre maximum</string>
|
||||||
<string name="max_history_items_summary">Limite le nombre d’éléments de l’historique par entrée</string>
|
<string name="max_history_items_summary">Limite le nombre d’éléments de l’historique par entrée</string>
|
||||||
<string name="max_history_size_title">Taille maximum</string>
|
<string name="max_history_size_title">Taille maximum</string>
|
||||||
<string name="max_history_size_summary">Limite la taille de l’historique (en octets) par entrée</string>
|
<string name="max_history_size_summary">Limite la taille de l’historique par entrée</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Recommander le renouvellement</string>
|
<string name="settings_database_recommend_changing_master_key_title">Recommander le renouvellement</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">Recommande le changement de la clé principale (jours)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">Recommande le changement de la clé principale (jours)</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">Forcer le renouvellement</string>
|
<string name="settings_database_force_changing_master_key_title">Forcer le renouvellement</string>
|
||||||
@@ -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>
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
<string name="read_only_warning">Ovisno o upravitelju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.</string>
|
<string name="read_only_warning">Ovisno o upravitelju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.</string>
|
||||||
<string name="contains_duplicate_uuid_procedure">Riješiti problem generiranjem novih UUID-ova za duplikate\?</string>
|
<string name="contains_duplicate_uuid_procedure">Riješiti problem generiranjem novih UUID-ova za duplikate\?</string>
|
||||||
<string name="root">Korijen</string>
|
<string name="root">Korijen</string>
|
||||||
<string name="memory_usage_explanation">Količina memorije (u bajtovima) koju će koristiti funkcija za generiranje ključeva.</string>
|
<string name="memory_usage_explanation">Količina memorije koju će koristiti funkcija za generiranje ključeva.</string>
|
||||||
<string name="do_not_kill_app">Ne zatvaraj aplikaciju …</string>
|
<string name="do_not_kill_app">Ne zatvaraj aplikaciju …</string>
|
||||||
<string name="sort_last_access_time">Zadnji pristup</string>
|
<string name="sort_last_access_time">Zadnji pristup</string>
|
||||||
<string name="special">Posebni znakovi</string>
|
<string name="special">Posebni znakovi</string>
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
<string name="recycle_bin_summary">Premijesti grupe i unose u koš za smeće prije brisanja</string>
|
<string name="recycle_bin_summary">Premijesti grupe i unose u koš za smeće prije brisanja</string>
|
||||||
<string name="recycle_bin_group_title">Grupa koša za smeće</string>
|
<string name="recycle_bin_group_title">Grupa koša za smeće</string>
|
||||||
<string name="max_history_items_summary">Ograniči broj spremljenih povijesti po unosu</string>
|
<string name="max_history_items_summary">Ograniči broj spremljenih povijesti po unosu</string>
|
||||||
<string name="max_history_size_summary">Ograniči veličinu povijesti (u bajtovima) po unosu</string>
|
<string name="max_history_size_summary">Ograniči veličinu povijesti po unosu</string>
|
||||||
<string name="allow_copy_password_title">Povjerenje međuspremniku</string>
|
<string name="allow_copy_password_title">Povjerenje međuspremniku</string>
|
||||||
<string name="allow_copy_password_summary">Dozvoli kopiranje lozinke i zaštićenih polja u međuspremnik</string>
|
<string name="allow_copy_password_summary">Dozvoli kopiranje lozinke i zaštićenih polja u međuspremnik</string>
|
||||||
<string name="allow_copy_password_warning">Upozorenje: Međuspremnik dijele sve aplikacije. Ako se kopiraju osjetljivi podaci, druga aplikacija ih može vidjeti.</string>
|
<string name="allow_copy_password_warning">Upozorenje: Međuspremnik dijele sve aplikacije. Ako se kopiraju osjetljivi podaci, druga aplikacija ih može vidjeti.</string>
|
||||||
@@ -527,4 +527,13 @@
|
|||||||
<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>
|
||||||
|
<string name="autofill_inline_suggestions_title">Umetnuti prijedlozi</string>
|
||||||
|
<string name="autofill_inline_suggestions_keyboard">Prijedlozi za automatsko popunjavanje su dodani.</string>
|
||||||
|
<string name="autofill_inline_suggestions_summary">Pokušaj prikazivanja prijedloga za automatsko popunjavanje izravno s kompatibilne tipkovnice</string>
|
||||||
|
<string name="warning_database_revoked">Pristup datoteci koju je opozvao upravljač datoteka. Zatvori bazu podataka i ponovo je otvori s njezinog mjesta.</string>
|
||||||
|
<string name="warning_database_info_changed_options">Prepiši vanjske promjene spremanjem baze podataka ili je ponovo učitaj s najnovijim promjenama.</string>
|
||||||
|
<string name="warning_database_info_changed">Podaci u datoteci tvoje baze podataka izmijenjeni su izvan programa.</string>
|
||||||
|
<string name="menu_reload_database">Ponovo učitaj bazu podataka</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
<string name="encryption_explanation">Az összes adathoz használt adatbázis-titkosítási algoritmus.</string>
|
<string name="encryption_explanation">Az összes adathoz használt adatbázis-titkosítási algoritmus.</string>
|
||||||
<string name="kdf_explanation">A kulcs előállításához a titkosítási algoritmushoz, a mesterkulcs átalakításra került egy véletlenszerűen sózott kulcselőállítási függvénnyel.</string>
|
<string name="kdf_explanation">A kulcs előállításához a titkosítási algoritmushoz, a mesterkulcs átalakításra került egy véletlenszerűen sózott kulcselőállítási függvénnyel.</string>
|
||||||
<string name="memory_usage">Memóriahasználat</string>
|
<string name="memory_usage">Memóriahasználat</string>
|
||||||
<string name="memory_usage_explanation">A kulcselőállítási függvényhez használt memóriamennyiség (bájtban).</string>
|
<string name="memory_usage_explanation">A kulcselőállítási függvényhez használt memóriamennyiség.</string>
|
||||||
<string name="parallelism">Párhuzamosság</string>
|
<string name="parallelism">Párhuzamosság</string>
|
||||||
<string name="parallelism_explanation">A kulcselőállítási függvény párhuzamosságának mértéke (azaz a szálak száma).</string>
|
<string name="parallelism_explanation">A kulcselőállítási függvény párhuzamosságának mértéke (azaz a szálak száma).</string>
|
||||||
<string name="sort_menu">Rendezés</string>
|
<string name="sort_menu">Rendezés</string>
|
||||||
@@ -398,7 +398,7 @@
|
|||||||
<string name="settings_database_force_changing_master_key_title">Megújítás kényszerítése</string>
|
<string name="settings_database_force_changing_master_key_title">Megújítás kényszerítése</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">A mesterkulcs módosításának javaslata (napban)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">A mesterkulcs módosításának javaslata (napban)</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Javasolt megújítás</string>
|
<string name="settings_database_recommend_changing_master_key_title">Javasolt megújítás</string>
|
||||||
<string name="max_history_size_summary">Korlátozza az előzmények méretét bejegyzésenként (bájtban)</string>
|
<string name="max_history_size_summary">Korlátozza az előzmények méretét bejegyzésenként</string>
|
||||||
<string name="max_history_size_title">Maximális méret</string>
|
<string name="max_history_size_title">Maximális méret</string>
|
||||||
<string name="max_history_items_summary">Korlátozza az előzmények számát bejegyzésenként</string>
|
<string name="max_history_items_summary">Korlátozza az előzmények számát bejegyzésenként</string>
|
||||||
<string name="max_history_items_title">Maximális szám</string>
|
<string name="max_history_items_title">Maximális szám</string>
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
<string name="encryption_explanation">Algoritmo di cifratura del database usato per tutti i dati.</string>
|
<string name="encryption_explanation">Algoritmo di cifratura del database usato per tutti i dati.</string>
|
||||||
<string name="kdf_explanation">Per generare la chiave per l\'algoritmo di cifratura, la chiave principale viene trasformata usando una funzione di derivazione della chiave (con un sale casuale).</string>
|
<string name="kdf_explanation">Per generare la chiave per l\'algoritmo di cifratura, la chiave principale viene trasformata usando una funzione di derivazione della chiave (con un sale casuale).</string>
|
||||||
<string name="memory_usage">Utilizzo di memoria</string>
|
<string name="memory_usage">Utilizzo di memoria</string>
|
||||||
<string name="memory_usage_explanation">Quantità di memoria (in byte) utilizzabili dalla funzione di derivazione della chiave.</string>
|
<string name="memory_usage_explanation">Quantità di memoria utilizzabili dalla funzione di derivazione della chiave.</string>
|
||||||
<string name="parallelism">Parallelismo</string>
|
<string name="parallelism">Parallelismo</string>
|
||||||
<string name="parallelism_explanation">Grado di parallelismo (cioè numero di thread) usato dalla funzione di derivazione della chiave.</string>
|
<string name="parallelism_explanation">Grado di parallelismo (cioè numero di thread) usato dalla funzione di derivazione della chiave.</string>
|
||||||
<string name="sort_menu">Ordina</string>
|
<string name="sort_menu">Ordina</string>
|
||||||
@@ -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>
|
||||||
@@ -413,7 +413,7 @@
|
|||||||
<string name="max_history_size_title">Dimensione massima</string>
|
<string name="max_history_size_title">Dimensione massima</string>
|
||||||
<string name="max_history_items_title">Numero massimo</string>
|
<string name="max_history_items_title">Numero massimo</string>
|
||||||
<string name="biometric_auto_open_prompt_title">Apri automaticamente la richiesta</string>
|
<string name="biometric_auto_open_prompt_title">Apri automaticamente la richiesta</string>
|
||||||
<string name="max_history_size_summary">Limita la dimensione (in byte) della cronologia per voce</string>
|
<string name="max_history_size_summary">Limita la dimensione della cronologia per voce</string>
|
||||||
<string name="max_history_items_summary">Limita il numero di elementi della cronologia per voce</string>
|
<string name="max_history_items_summary">Limita il numero di elementi della cronologia per voce</string>
|
||||||
<string name="recycle_bin_group_title">Gruppo cestino</string>
|
<string name="recycle_bin_group_title">Gruppo cestino</string>
|
||||||
<string name="database_data_compression_summary">La compressione dei dati riduce le dimensioni del database</string>
|
<string name="database_data_compression_summary">La compressione dei dati riduce le dimensioni del database</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>
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
<string name="rounds">変換ラウンド</string>
|
<string name="rounds">変換ラウンド</string>
|
||||||
<string name="rounds_explanation">変換ラウンドを増やすことでブルート フォース攻撃に対する保護が強化されますが、読み込みと保存が本当に遅くなる可能性があります。</string>
|
<string name="rounds_explanation">変換ラウンドを増やすことでブルート フォース攻撃に対する保護が強化されますが、読み込みと保存が本当に遅くなる可能性があります。</string>
|
||||||
<string name="memory_usage">メモリ使用量</string>
|
<string name="memory_usage">メモリ使用量</string>
|
||||||
<string name="memory_usage_explanation">鍵導出関数が使用するメモリの量(バイト単位)です。</string>
|
<string name="memory_usage_explanation">鍵導出関数が使用するメモリの量です。</string>
|
||||||
<string name="parallelism">並列処理</string>
|
<string name="parallelism">並列処理</string>
|
||||||
<string name="parallelism_explanation">鍵導出関数が使用する並列処理のレベル(スレッド数)です。</string>
|
<string name="parallelism_explanation">鍵導出関数が使用する並列処理のレベル(スレッド数)です。</string>
|
||||||
<string name="saving_database">データベースを保存しています…</string>
|
<string name="saving_database">データベースを保存しています…</string>
|
||||||
@@ -325,7 +325,7 @@
|
|||||||
<string name="max_history_items_title">最大値</string>
|
<string name="max_history_items_title">最大値</string>
|
||||||
<string name="max_history_items_summary">エントリーあたりの履歴項目の数を制限します</string>
|
<string name="max_history_items_summary">エントリーあたりの履歴項目の数を制限します</string>
|
||||||
<string name="max_history_size_title">最大サイズ</string>
|
<string name="max_history_size_title">最大サイズ</string>
|
||||||
<string name="max_history_size_summary">エントリーあたりの履歴のサイズ(バイト単位)を制限します</string>
|
<string name="max_history_size_summary">エントリーあたりの履歴のサイズを制限します</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">更新を推奨</string>
|
<string name="settings_database_recommend_changing_master_key_title">更新を推奨</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">マスターキーの変更を推奨します(日数)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">マスターキーの変更を推奨します(日数)</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">更新を強制</string>
|
<string name="settings_database_force_changing_master_key_title">更新を強制</string>
|
||||||
@@ -541,4 +541,15 @@
|
|||||||
<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>
|
||||||
|
<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>
|
||||||
|
<string name="error_rebuild_list">リストを正しく再構築できません。</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<string name="entry_keyfile">Rakto failas</string>
|
<string name="entry_keyfile">Rakto failas</string>
|
||||||
<string name="search">Įrašo pavadinimas/aprašymas</string>
|
<string name="search">Įrašo pavadinimas/aprašymas</string>
|
||||||
<string name="menu_change_key_settings">Pakeisti master raktą</string>
|
<string name="menu_change_key_settings">Pakeisti master raktą</string>
|
||||||
<string name="entry_accessed">Naudota:</string>
|
<string name="entry_accessed">Naudota</string>
|
||||||
<string name="hide_password_title">Maskuoti slaptažodį</string>
|
<string name="hide_password_title">Maskuoti slaptažodį</string>
|
||||||
<string name="space">Tarpas</string>
|
<string name="space">Tarpas</string>
|
||||||
<string name="special">Specialus</string>
|
<string name="special">Specialus</string>
|
||||||
|
|||||||
@@ -300,7 +300,7 @@
|
|||||||
<string name="sort_groups_before">മുമ്പുള്ള ഗ്രൂപ്പുകൾ</string>
|
<string name="sort_groups_before">മുമ്പുള്ള ഗ്രൂപ്പുകൾ</string>
|
||||||
<string name="sort_ascending">ആദ്യത്തേത് ഏറ്റവും കുറഞ്ഞത്↓</string>
|
<string name="sort_ascending">ആദ്യത്തേത് ഏറ്റവും കുറഞ്ഞത്↓</string>
|
||||||
<string name="parallelism_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന സമാന്തരതയുടെ ബിരുദം (അതായത് ത്രെഡുകളുടെ എണ്ണം).</string>
|
<string name="parallelism_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന സമാന്തരതയുടെ ബിരുദം (അതായത് ത്രെഡുകളുടെ എണ്ണം).</string>
|
||||||
<string name="memory_usage_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന മെമ്മറിയുടെ അളവ് (ബൈറ്റുകളിൽ).</string>
|
<string name="memory_usage_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന മെമ്മറിയുടെ അളവ് .</string>
|
||||||
<string name="encryption_explanation">എല്ലാ ഡാറ്റയ്ക്കും ഉപയോഗിക്കുന്ന ഡാറ്റാബേസ് എൻക്രിപ്ഷൻ അൽഗോരിതം.</string>
|
<string name="encryption_explanation">എല്ലാ ഡാറ്റയ്ക്കും ഉപയോഗിക്കുന്ന ഡാറ്റാബേസ് എൻക്രിപ്ഷൻ അൽഗോരിതം.</string>
|
||||||
<string name="hide_broken_locations_summary">സമീപകാല ഡാറ്റാബേസുകളുടെ പട്ടികയിൽ നിന്ന് തകർന്ന ലിങ്കുകൾ മറയ്ക്കുക</string>
|
<string name="hide_broken_locations_summary">സമീപകാല ഡാറ്റാബേസുകളുടെ പട്ടികയിൽ നിന്ന് തകർന്ന ലിങ്കുകൾ മറയ്ക്കുക</string>
|
||||||
<string name="hide_broken_locations_title">തകർന്ന ഡാറ്റാബേസ് ലിങ്കുകൾ മറയ്ക്കുക</string>
|
<string name="hide_broken_locations_title">തകർന്ന ഡാറ്റാബേസ് ലിങ്കുകൾ മറയ്ക്കുക</string>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@
|
|||||||
<string name="rounds">Krypteringsomganger</string>
|
<string name="rounds">Krypteringsomganger</string>
|
||||||
<string name="rounds_explanation">Flere krypteringsomganger gir ytterligere beskyttelse mot råmaktsangrep, men kan virkelig sakke ned innlasting og lagring.</string>
|
<string name="rounds_explanation">Flere krypteringsomganger gir ytterligere beskyttelse mot råmaktsangrep, men kan virkelig sakke ned innlasting og lagring.</string>
|
||||||
<string name="memory_usage">Minnebruk</string>
|
<string name="memory_usage">Minnebruk</string>
|
||||||
<string name="memory_usage_explanation">Mengden minne (i byte) brukt til nøkkelutledelsesfunksjonen.</string>
|
<string name="memory_usage_explanation">Mengden minne brukt til nøkkelutledelsesfunksjonen.</string>
|
||||||
<string name="parallelism">Parallellitet</string>
|
<string name="parallelism">Parallellitet</string>
|
||||||
<string name="parallelism_explanation">Graden av parallellitet (dvs. antallet tråder) brukt av nøkkelutledingsfunksjonen.</string>
|
<string name="parallelism_explanation">Graden av parallellitet (dvs. antallet tråder) brukt av nøkkelutledingsfunksjonen.</string>
|
||||||
<string name="saving_database">Lagrer database…</string>
|
<string name="saving_database">Lagrer database…</string>
|
||||||
|
|||||||
@@ -180,7 +180,7 @@
|
|||||||
<string name="encryption_explanation">Database-encryptie-algoritme dat voor alle gegevens wordt gebruikt.</string>
|
<string name="encryption_explanation">Database-encryptie-algoritme dat voor alle gegevens wordt gebruikt.</string>
|
||||||
<string name="kdf_explanation">Om de sleutel voor het algoritme te kunnen genereren, wordt de hoofdsleutel getransformeerd middels een willekeurige afleidingsfunctie.</string>
|
<string name="kdf_explanation">Om de sleutel voor het algoritme te kunnen genereren, wordt de hoofdsleutel getransformeerd middels een willekeurige afleidingsfunctie.</string>
|
||||||
<string name="memory_usage">Geheugengebruik</string>
|
<string name="memory_usage">Geheugengebruik</string>
|
||||||
<string name="memory_usage_explanation">De hoeveelheid geheugen (bytes) dat de afleidingsfunctie mag gebruiken.</string>
|
<string name="memory_usage_explanation">De hoeveelheid geheugen dat de afleidingsfunctie mag gebruiken.</string>
|
||||||
<string name="parallelism">Parallellen</string>
|
<string name="parallelism">Parallellen</string>
|
||||||
<string name="parallelism_explanation">Het aantal parallellen (aantal threads) dat de afleidingsfunctie mag gebruiken.</string>
|
<string name="parallelism_explanation">Het aantal parallellen (aantal threads) dat de afleidingsfunctie mag gebruiken.</string>
|
||||||
<string name="sort_menu">Sorteren</string>
|
<string name="sort_menu">Sorteren</string>
|
||||||
@@ -397,7 +397,7 @@
|
|||||||
<string name="max_history_items_title">Maximum aantal</string>
|
<string name="max_history_items_title">Maximum aantal</string>
|
||||||
<string name="max_history_items_summary">Beperk het aantal geschiedenisitems per item</string>
|
<string name="max_history_items_summary">Beperk het aantal geschiedenisitems per item</string>
|
||||||
<string name="max_history_size_title">Maximum</string>
|
<string name="max_history_size_title">Maximum</string>
|
||||||
<string name="max_history_size_summary">Geschiedenis (bytes) per item beperken</string>
|
<string name="max_history_size_summary">Geschiedenis per item beperken</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">Vernieuwing aanbevelen</string>
|
<string name="settings_database_recommend_changing_master_key_title">Vernieuwing aanbevelen</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_summary">Aanbeveling de hoofdsleutel te wijzigen (dagen)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">Aanbeveling de hoofdsleutel te wijzigen (dagen)</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">Vernieuwing afdwingen</string>
|
<string name="settings_database_force_changing_master_key_title">Vernieuwing afdwingen</string>
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
<string name="encryption_explanation">Algorytm szyfrowania bazy danych używany dla wszystkich danych.</string>
|
<string name="encryption_explanation">Algorytm szyfrowania bazy danych używany dla wszystkich danych.</string>
|
||||||
<string name="kdf_explanation">Aby wygenerować klucz dla algorytmu szyfrowania, klucz główny jest transformowany przy użyciu losowo solonej funkcji wyprowadzania klucza.</string>
|
<string name="kdf_explanation">Aby wygenerować klucz dla algorytmu szyfrowania, klucz główny jest transformowany przy użyciu losowo solonej funkcji wyprowadzania klucza.</string>
|
||||||
<string name="memory_usage">Użycie pamięci</string>
|
<string name="memory_usage">Użycie pamięci</string>
|
||||||
<string name="memory_usage_explanation">Ilość pamięci (w bajtach) do użycia przez funkcję wyprowadzania klucza.</string>
|
<string name="memory_usage_explanation">Ilość pamięci do użycia przez funkcję wyprowadzania klucza.</string>
|
||||||
<string name="parallelism">Równoległy</string>
|
<string name="parallelism">Równoległy</string>
|
||||||
<string name="parallelism_explanation">Stopień równoległości (tj. Liczba wątków) wykorzystywany przez funkcję wyprowadzania klucza.</string>
|
<string name="parallelism_explanation">Stopień równoległości (tj. Liczba wątków) wykorzystywany przez funkcję wyprowadzania klucza.</string>
|
||||||
<string name="sort_menu">Sortuj</string>
|
<string name="sort_menu">Sortuj</string>
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
<string name="advanced_unlock_explanation_summary">Użyj zaawansowanego odblokowywania w celu łatwiejszego otwierania bazy danych</string>
|
<string name="advanced_unlock_explanation_summary">Użyj zaawansowanego odblokowywania w celu łatwiejszego otwierania bazy danych</string>
|
||||||
<string name="database_data_compression_summary">Kompresja danych zmniejsza rozmiar bazy danych</string>
|
<string name="database_data_compression_summary">Kompresja danych zmniejsza rozmiar bazy danych</string>
|
||||||
<string name="max_history_items_title">Maksymalna liczba</string>
|
<string name="max_history_items_title">Maksymalna liczba</string>
|
||||||
<string name="max_history_size_summary">Ogranicz rozmiar historii (w bajtach) na wpis</string>
|
<string name="max_history_size_summary">Ogranicz rozmiar historii na wpis</string>
|
||||||
<string name="settings_database_force_changing_master_key_title">Wymuś odnowienie</string>
|
<string name="settings_database_force_changing_master_key_title">Wymuś odnowienie</string>
|
||||||
<string name="settings_database_force_changing_master_key_next_time_title">Wymuś odnowienie następnym razem</string>
|
<string name="settings_database_force_changing_master_key_next_time_title">Wymuś odnowienie następnym razem</string>
|
||||||
<string name="settings_database_force_changing_master_key_next_time_summary">Wymagaj zmiany klucza głównego następnym razem (raz)</string>
|
<string name="settings_database_force_changing_master_key_next_time_summary">Wymagaj zmiany klucza głównego następnym razem (raz)</string>
|
||||||
@@ -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ć monitu odblokowania zaawansowanego.</string>
|
||||||
|
<string name="open_advanced_unlock_prompt_store_credential">Otwórz monit odblokowania zaawansowanego, aby odblokować bazę danych</string>
|
||||||
|
<string name="open_advanced_unlock_prompt_unlock_database">Otwórz monit odblokowania zaawansowanego, 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>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user