mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
536e038306 | ||
|
|
9e1f6d29a5 | ||
|
|
7a9469e59d | ||
|
|
c12eb3d643 | ||
|
|
da0f02e536 | ||
|
|
04bcc6631c | ||
|
|
698e3b7fb1 | ||
|
|
6de02384c1 | ||
|
|
df3bd7e0a1 | ||
|
|
c8c232639f | ||
|
|
192d6eedd0 | ||
|
|
9cae3f0794 | ||
|
|
a680db9707 | ||
|
|
fe526089d7 | ||
|
|
dfd7ade416 | ||
|
|
3cd65345c5 | ||
|
|
2d398908de | ||
|
|
756454abc3 | ||
|
|
b7619b45b1 | ||
|
|
1369a3cad9 | ||
|
|
f46c062c4e | ||
|
|
0a0abef4d4 | ||
|
|
3a8245ee74 | ||
|
|
7be554a378 | ||
|
|
6c37f7b12c | ||
|
|
3a67ec09d5 | ||
|
|
dca800b1bb | ||
|
|
70665f110d | ||
|
|
3b39cafb99 | ||
|
|
2b5ecb2f84 | ||
|
|
e397b92c36 | ||
|
|
e273eb6e03 | ||
|
|
28b624afa3 | ||
|
|
fb1e6cdc3f | ||
|
|
8b6499d040 | ||
|
|
054af507ad | ||
|
|
ac9bb9b666 | ||
|
|
809e1929e5 | ||
|
|
a1b1338d67 | ||
|
|
bd4cacfab1 | ||
|
|
e0343bdc55 | ||
|
|
b743d004e2 | ||
|
|
4b20e035b2 | ||
|
|
afe5fddc50 | ||
|
|
d68ca1b51f | ||
|
|
061b087229 | ||
|
|
bb3a379965 | ||
|
|
593b5c6338 | ||
|
|
56f8a1bf9f | ||
|
|
962b547b36 | ||
|
|
6df8ff4310 | ||
|
|
52f17140b8 | ||
|
|
75c2bb4a87 | ||
|
|
f36f6c3155 | ||
|
|
b88b92c5b0 | ||
|
|
d2c569c4f0 | ||
|
|
cb1316564e | ||
|
|
245d3f7df2 | ||
|
|
3729b3c5a0 | ||
|
|
7ce5eb3c27 | ||
|
|
43defea85e | ||
|
|
8470c4e39b | ||
|
|
a41afb7f1e | ||
|
|
32d9cfbe29 | ||
|
|
7210652567 | ||
|
|
ab15967ad7 | ||
|
|
44df4ec181 | ||
|
|
7afe356082 | ||
|
|
87597553b8 | ||
|
|
27e5f58d5e | ||
|
|
762c946d35 | ||
|
|
21a927e3e9 | ||
|
|
f93bb7436a | ||
|
|
6294fddbba | ||
|
|
c5719dfaf2 | ||
|
|
673fd67f15 | ||
|
|
25524c48e9 | ||
|
|
631b924c33 | ||
|
|
fba12bc278 | ||
|
|
bf7e014f8c | ||
|
|
40e1607698 | ||
|
|
4a132f06fe | ||
|
|
0396dd975d | ||
|
|
a6b20455ef | ||
|
|
ca148ef546 | ||
|
|
25df86606c | ||
|
|
811f33eb3f | ||
|
|
ca7e2ed89d | ||
|
|
6f4cd79e2c | ||
|
|
da1caf4b8b | ||
|
|
4a0a8e44ca | ||
|
|
6bc2c3481b | ||
|
|
dc75837ac7 | ||
|
|
9849b0a1da | ||
|
|
2c15a1ddd6 | ||
|
|
98eb9976cf | ||
|
|
0d9a5810b1 | ||
|
|
1adaa137a5 | ||
|
|
44a428d15a | ||
|
|
5416a7942a | ||
|
|
9e0024baf5 | ||
|
|
8d47ce38c2 | ||
|
|
80af43c0ca | ||
|
|
225f8243c2 | ||
|
|
68bc118add | ||
|
|
abbc584402 | ||
|
|
6635594639 | ||
|
|
10db77d402 | ||
|
|
000fd7e520 | ||
|
|
c8ced4ae59 | ||
|
|
9209ca9af7 | ||
|
|
c7bd90c610 | ||
|
|
fceb9c3547 | ||
|
|
030c49b571 | ||
|
|
f2d6a6a536 | ||
|
|
8f61521f05 | ||
|
|
89af7ec5d0 | ||
|
|
362f1aebed | ||
|
|
5226527cec | ||
|
|
b8464cd0e5 | ||
|
|
46e7b04d66 | ||
|
|
73111b770f | ||
|
|
995d485700 | ||
|
|
5ebbbef667 | ||
|
|
a7a93fa2a2 |
12
CHANGELOG
12
CHANGELOG
@@ -1,3 +1,15 @@
|
||||
KeePassDX(4.1.4)
|
||||
* Fix UnlockManager #2098 #2101
|
||||
* Auto device unlock prompt #2105
|
||||
* Small fixes ##2066
|
||||
|
||||
KeePassDX(4.1.3)
|
||||
* Fix Autofill Registration #2089
|
||||
* Fix Biometric errors #2081
|
||||
* Fixed timestamp in copy file #1981 #1983
|
||||
* Fix Template Email #1986
|
||||
* Fix Search #2096
|
||||
|
||||
KeePassDX(4.1.2)
|
||||
* Fix URL search #1940 #1946 #2003 #2040 #2044
|
||||
* Fix Autofill popup #2054
|
||||
|
||||
75
Gemfile.lock
75
Gemfile.lock
@@ -9,31 +9,35 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.1009.0)
|
||||
aws-sdk-core (3.213.0)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1146.0)
|
||||
aws-sdk-core (3.229.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.95.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
logger
|
||||
aws-sdk-kms (1.110.0)
|
||||
aws-sdk-core (~> 3, >= 3.228.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.171.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-s3 (1.196.1)
|
||||
aws-sdk-core (~> 3, >= 3.228.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.1)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
base64 (0.3.0)
|
||||
bigdecimal (3.2.2)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
@@ -55,11 +59,11 @@ GEM
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-multipart (1.1.1)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
@@ -67,8 +71,8 @@ GEM
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.225.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -108,7 +112,7 @@ GEM
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty (~> 0.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-plugin-versioning_android (0.1.1)
|
||||
fastlane-sirp (1.0.0)
|
||||
@@ -130,12 +134,12 @@ GEM
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-core (1.8.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-errors (1.5.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
@@ -151,36 +155,39 @@ GEM
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.7)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.8.2)
|
||||
jwt (2.9.3)
|
||||
json (2.13.2)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multi_json (1.17.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.2.1)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (6.0.1)
|
||||
rake (13.2.1)
|
||||
plist (3.7.2)
|
||||
public_suffix (6.0.2)
|
||||
rake (13.3.0)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.9)
|
||||
rouge (2.0.7)
|
||||
rexml (3.4.1)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
signet (0.20.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
@@ -207,8 +214,8 @@ GEM
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
@@ -220,4 +227,4 @@ DEPENDENCIES
|
||||
fastlane-plugin-versioning_android
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.10
|
||||
2.6.9
|
||||
|
||||
@@ -54,7 +54,7 @@ Optional visual styles are accessible after a contribution (and a congratulatory
|
||||
|--------|--------|---------|
|
||||
| [Google Play](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free) |  | Free + [Pro](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro) |
|
||||
| [F-Droid](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) |  | Libre |
|
||||
| [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.free) |  | Free |
|
||||
| [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.free) |  | Free & [Libre](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.libre) |
|
||||
| [GitHub](https://github.com/Kunzisoft/KeePassDX/releases) / [Obtainium](https://github.com/ImranR98/Obtainium) |  | Free & Libre |
|
||||
|
||||
## Package authenticity from GitHub
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 34
|
||||
versionCode = 134
|
||||
versionName = "4.1.2"
|
||||
versionCode = 136
|
||||
versionName = "4.1.4"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
|
||||
@@ -124,41 +124,41 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
if (autofillComponent == null) {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
} else if (!KeeAutofillService.autofillAllowedFor(
|
||||
} else if (KeeAutofillService.autofillAllowedFor(
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = this
|
||||
)) {
|
||||
// If database is open
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ openedDatabase, items ->
|
||||
// Items found
|
||||
AutofillHelper.buildResponseAndSetResult(this, openedDatabase, items)
|
||||
finish()
|
||||
},
|
||||
{ openedDatabase ->
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForAutofillResult(this,
|
||||
openedDatabase,
|
||||
mAutofillActivityResultLauncher,
|
||||
autofillComponent,
|
||||
searchInfo,
|
||||
false)
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
FileDatabaseSelectActivity.launchForAutofillResult(this,
|
||||
mAutofillActivityResultLauncher,
|
||||
autofillComponent,
|
||||
searchInfo)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showBlockRestartMessage()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
} else {
|
||||
// If database is open
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ openedDatabase, items ->
|
||||
// Items found
|
||||
AutofillHelper.buildResponseAndSetResult(this, openedDatabase, items)
|
||||
finish()
|
||||
},
|
||||
{ openedDatabase ->
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForAutofillResult(this,
|
||||
openedDatabase,
|
||||
mAutofillActivityResultLauncher,
|
||||
autofillComponent,
|
||||
searchInfo,
|
||||
false)
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
FileDatabaseSelectActivity.launchForAutofillResult(this,
|
||||
mAutofillActivityResultLauncher,
|
||||
autofillComponent,
|
||||
searchInfo)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,40 +169,40 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = this
|
||||
)) {
|
||||
showBlockRestartMessage()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
} else {
|
||||
)) {
|
||||
val readOnly = database?.isReadOnly != false
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ openedDatabase, _ ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(this,
|
||||
openedDatabase,
|
||||
registerInfo)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
},
|
||||
{ openedDatabase ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(this,
|
||||
openedDatabase,
|
||||
registerInfo)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
FileDatabaseSelectActivity.launchForRegistration(this,
|
||||
registerInfo)
|
||||
database,
|
||||
searchInfo,
|
||||
{ openedDatabase, _ ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(this,
|
||||
openedDatabase,
|
||||
registerInfo)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
},
|
||||
{ openedDatabase ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(this,
|
||||
openedDatabase,
|
||||
registerInfo)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
FileDatabaseSelectActivity.launchForRegistration(this,
|
||||
registerInfo)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showBlockRestartMessage()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -316,6 +316,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
|
||||
private fun launchPasswordActivityWithPath(databaseUri: Uri) {
|
||||
launchPasswordActivity(databaseUri, null, null)
|
||||
// Delete flickering for kitkat <=
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
overridePendingTransition(0, 0)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.view.updateLockPaddingStart
|
||||
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.GroupViewModel
|
||||
import org.joda.time.Instant
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
|
||||
class GroupActivity : DatabaseLockActivity(),
|
||||
@@ -343,7 +343,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
mExternalFileHelper?.createDocument(
|
||||
getString(R.string.database_file_name_default) +
|
||||
"_" +
|
||||
Instant.now().toString() +
|
||||
LocalDateTime.now().toString() +
|
||||
mDatabase?.defaultFileExtension)
|
||||
}
|
||||
R.id.menu_lock_all -> {
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
@@ -43,25 +42,33 @@ import androidx.appcompat.widget.Toolbar
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity.Companion.UI_VISIBLE_DURING_LOCK
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockFragment
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockManager
|
||||
import com.kunzisoft.keepass.biometric.deviceUnlockError
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.MainCredential
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.model.*
|
||||
import com.kunzisoft.keepass.model.CipherDecryptDatabase
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.model.CredentialStorage
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_DATABASE_KEY
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
|
||||
@@ -79,12 +86,14 @@ import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||
import com.kunzisoft.keepass.view.MainCredentialView
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.DeviceUnlockState
|
||||
import com.kunzisoft.keepass.viewmodels.DeviceUnlockViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
|
||||
class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
|
||||
class MainCredentialActivity : DatabaseModeActivity() {
|
||||
|
||||
// Views
|
||||
private var toolbar: Toolbar? = null
|
||||
@@ -95,10 +104,10 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
private var confirmButtonView: Button? = null
|
||||
private var infoContainerView: ViewGroup? = null
|
||||
private lateinit var coordinatorLayout: CoordinatorLayout
|
||||
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
|
||||
private var deviceUnlockFragment: DeviceUnlockFragment? = null
|
||||
|
||||
private val mDatabaseFileViewModel: DatabaseFileViewModel by viewModels()
|
||||
private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by viewModels()
|
||||
private val mDeviceUnlockViewModel: DeviceUnlockViewModel by viewModels()
|
||||
|
||||
private val mPasswordActivityEducation = PasswordActivityEducation(this)
|
||||
|
||||
@@ -166,21 +175,14 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
}
|
||||
|
||||
// Listen password checkbox to init advanced unlock and confirmation button
|
||||
mainCredentialView?.onPasswordChecked =
|
||||
CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
mAdvancedUnlockViewModel.checkUnlockAvailability()
|
||||
enableConfirmationButton()
|
||||
}
|
||||
mainCredentialView?.onKeyFileChecked =
|
||||
CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
|
||||
enableConfirmationButton()
|
||||
}
|
||||
mainCredentialView?.onHardwareKeyChecked =
|
||||
CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
|
||||
enableConfirmationButton()
|
||||
}
|
||||
mainCredentialView?.onConditionToStoreCredentialChanged = { _, verified ->
|
||||
mDeviceUnlockViewModel.checkConditionToStoreCredential(
|
||||
condition = verified,
|
||||
databaseFileUri = mDatabaseFileUri
|
||||
)
|
||||
// TODO Async by ViewModel
|
||||
enableConfirmationButton()
|
||||
}
|
||||
|
||||
// Observe if default database
|
||||
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
|
||||
@@ -228,17 +230,50 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
|
||||
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri, hardwareKey)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mDeviceUnlockViewModel.uiState.collect { uiState ->
|
||||
// New value received
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
uiState.credentialRequiredCipher?.let { cipher ->
|
||||
mDeviceUnlockViewModel.encryptCredential(
|
||||
credential = getCredentialForEncryption(),
|
||||
cipher = cipher
|
||||
)
|
||||
}
|
||||
}
|
||||
uiState.cipherEncryptDatabase?.let { cipherEncryptDatabase ->
|
||||
onCredentialEncrypted(cipherEncryptDatabase)
|
||||
mDeviceUnlockViewModel.consumeCredentialEncrypted()
|
||||
}
|
||||
uiState.cipherDecryptDatabase?.let { cipherDecryptDatabase ->
|
||||
onCredentialDecrypted(cipherDecryptDatabase)
|
||||
mDeviceUnlockViewModel.consumeCredentialDecrypted()
|
||||
}
|
||||
uiState.exception?.let { error ->
|
||||
Snackbar.make(
|
||||
coordinatorLayout,
|
||||
deviceUnlockError(error, this@MainCredentialActivity),
|
||||
Snackbar.LENGTH_LONG
|
||||
).asError().show()
|
||||
mDeviceUnlockViewModel.exceptionShown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Init Biometric elements only if allowed
|
||||
if (PreferencesUtil.isAdvancedUnlockEnable(this)) {
|
||||
advancedUnlockFragment = supportFragmentManager
|
||||
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
|
||||
if (advancedUnlockFragment == null) {
|
||||
advancedUnlockFragment = AdvancedUnlockFragment().also {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& PreferencesUtil.isAdvancedUnlockEnable(this)) {
|
||||
deviceUnlockFragment = supportFragmentManager
|
||||
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? DeviceUnlockFragment?
|
||||
if (deviceUnlockFragment == null) {
|
||||
deviceUnlockFragment = DeviceUnlockFragment().also {
|
||||
supportFragmentManager.commit {
|
||||
replace(
|
||||
R.id.fragment_advanced_unlock_container_view,
|
||||
@@ -258,11 +293,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
|
||||
}
|
||||
|
||||
// Don't allow auto open prompt if lock become when UI visible
|
||||
if (DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true) {
|
||||
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false
|
||||
}
|
||||
|
||||
mDatabaseFileUri?.let { databaseFileUri ->
|
||||
mDatabaseFileViewModel.loadDatabaseFile(databaseFileUri)
|
||||
}
|
||||
@@ -296,9 +326,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
super.onDatabaseActionFinished(database, actionTask, result)
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_LOAD_TASK -> {
|
||||
// Recheck advanced unlock if error
|
||||
mAdvancedUnlockViewModel.initAdvancedUnlockMode()
|
||||
|
||||
if (result.isSuccess) {
|
||||
launchGroupActivityIfLoaded(database)
|
||||
} else {
|
||||
@@ -389,6 +416,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
private fun launchGroupActivityIfLoaded(database: ContextualDatabase) {
|
||||
// Check if database really loaded
|
||||
if (database.loaded) {
|
||||
mDeviceUnlockViewModel.allowAutoOpenBiometricPrompt = true
|
||||
clearCredentialsViews(clearKeyFile = true, clearHardwareKey = true)
|
||||
GroupActivity.launch(this,
|
||||
database,
|
||||
@@ -400,23 +428,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
}
|
||||
}
|
||||
|
||||
override fun retrieveCredentialForEncryption(): ByteArray {
|
||||
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
|
||||
?: byteArrayOf()
|
||||
}
|
||||
|
||||
override fun conditionToStoreCredential(): Boolean {
|
||||
return mainCredentialView?.conditionToStoreCredential() == true
|
||||
}
|
||||
|
||||
override fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
|
||||
// Load the database if password is registered with biometric
|
||||
loadDatabase(mDatabaseFileUri,
|
||||
mainCredentialView?.getMainCredential(),
|
||||
cipherEncryptDatabase
|
||||
)
|
||||
}
|
||||
|
||||
private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener {
|
||||
override fun passwordToStore(password: String?): ByteArray? {
|
||||
return password?.toByteArray()
|
||||
@@ -433,7 +444,20 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
|
||||
private fun getCredentialForEncryption(): ByteArray {
|
||||
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
|
||||
?: byteArrayOf()
|
||||
}
|
||||
|
||||
private fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
|
||||
// Load the database if password is registered with biometric
|
||||
loadDatabase(mDatabaseFileUri,
|
||||
mainCredentialView?.getMainCredential(),
|
||||
cipherEncryptDatabase
|
||||
)
|
||||
}
|
||||
|
||||
private fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
|
||||
// Load the database if password is retrieve from biometric
|
||||
// Retrieve from biometric
|
||||
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()
|
||||
@@ -485,7 +509,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
loadDatabase()
|
||||
} else {
|
||||
// Init Biometric elements
|
||||
mAdvancedUnlockViewModel.databaseFileLoaded(databaseFileUri)
|
||||
mDeviceUnlockViewModel.databaseFileLoaded(databaseFileUri)
|
||||
}
|
||||
|
||||
enableConfirmationButton()
|
||||
@@ -515,8 +539,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
|
||||
override fun onPause() {
|
||||
// Reinit locking activity UI variable
|
||||
DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
|
||||
|
||||
UI_VISIBLE_DURING_LOCK = false
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
@@ -645,7 +668,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !readOnlyEducationPerformed) {
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(this)
|
||||
val biometricCanAuthenticate = DeviceUnlockManager.canAuthenticate(this)
|
||||
if ((biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||
&& advancedUnlockButton != null) {
|
||||
|
||||
@@ -47,10 +47,14 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||
import com.kunzisoft.keepass.utils.LockReceiver
|
||||
import com.kunzisoft.keepass.utils.closeDatabase
|
||||
import com.kunzisoft.keepass.utils.registerLockReceiver
|
||||
import com.kunzisoft.keepass.utils.unregisterLockReceiver
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.viewmodels.NodesViewModel
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
PasswordEncodingDialogFragment.Listener {
|
||||
@@ -184,8 +188,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
mLockReceiver = LockReceiver {
|
||||
mDatabase = null
|
||||
closeDatabase(database)
|
||||
if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null)
|
||||
LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE
|
||||
UI_VISIBLE_DURING_LOCK = UI_VISIBLE
|
||||
mExitLock = true
|
||||
closeOptionsMenu()
|
||||
finish()
|
||||
@@ -414,7 +417,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
|
||||
invalidateOptionsMenu()
|
||||
|
||||
LOCKING_ACTIVITY_UI_VISIBLE = true
|
||||
UI_VISIBLE = true
|
||||
}
|
||||
|
||||
protected fun checkTimeAndLockIfTimeoutOrResetTimeout(action: (() -> Unit)? = null) {
|
||||
@@ -429,7 +432,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
LOCKING_ACTIVITY_UI_VISIBLE = false
|
||||
UI_VISIBLE = false
|
||||
|
||||
super.onPause()
|
||||
|
||||
@@ -481,8 +484,8 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
|
||||
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
|
||||
|
||||
private var LOCKING_ACTIVITY_UI_VISIBLE = false
|
||||
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
|
||||
var UI_VISIBLE: Boolean = false
|
||||
var UI_VISIBLE_DURING_LOCK: Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.activities.stylish
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -77,7 +78,18 @@ abstract class StylishActivity : AppCompatActivity() {
|
||||
startActivity(intent)
|
||||
}
|
||||
finish()
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
overrideActivityTransition(
|
||||
OVERRIDE_TRANSITION_OPEN,
|
||||
android.R.anim.fade_in,
|
||||
android.R.anim.fade_out
|
||||
)
|
||||
else
|
||||
overridePendingTransition(
|
||||
android.R.anim.fade_in,
|
||||
android.R.anim.fade_out
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -177,14 +177,18 @@ class CipherDatabaseAction(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun containsCipherDatabase(databaseUri: Uri,
|
||||
fun containsCipherDatabase(databaseUri: Uri?,
|
||||
contains: (Boolean) -> Unit) {
|
||||
getCipherDatabase(databaseUri) {
|
||||
contains.invoke(it != null)
|
||||
if (databaseUri == null) {
|
||||
contains.invoke(false)
|
||||
} else {
|
||||
getCipherDatabase(databaseUri) {
|
||||
contains.invoke(it != null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetCipherParameters(databaseUri: Uri) {
|
||||
fun resetCipherParameters(databaseUri: Uri?) {
|
||||
containsCipherDatabase(databaseUri) { contains ->
|
||||
if (contains) {
|
||||
mBinder?.resetTimer()
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import javax.crypto.Cipher
|
||||
|
||||
data class AdvancedUnlockCryptoPrompt(var cipher: Cipher,
|
||||
@StringRes var promptTitleId: Int,
|
||||
@StringRes var promptDescriptionId: Int? = null,
|
||||
var isDeviceCredentialOperation: Boolean,
|
||||
var isBiometricOperation: Boolean)
|
||||
@@ -1,678 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
|
||||
import com.kunzisoft.keepass.model.CipherDecryptDatabase
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.model.CredentialStorage
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
import com.kunzisoft.keepass.view.hideByFading
|
||||
import com.kunzisoft.keepass.view.showByFading
|
||||
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
|
||||
|
||||
private var mBuilderListener: BuilderListener? = null
|
||||
|
||||
private var mAdvancedUnlockEnabled = false
|
||||
private var mAutoOpenPromptEnabled = false
|
||||
|
||||
private var advancedUnlockManager: AdvancedUnlockManager? = null
|
||||
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE
|
||||
private var mAdvancedUnlockInfoView: AdvancedUnlockInfoView? = null
|
||||
|
||||
var databaseFileUri: Uri? = null
|
||||
private set
|
||||
|
||||
// TODO Retrieve credential storage from app database
|
||||
var credentialDatabaseStorage: CredentialStorage = CredentialStorage.DEFAULT
|
||||
|
||||
// Variable to check if the prompt can be open (if the right activity is currently shown)
|
||||
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
|
||||
private var allowOpenBiometricPrompt = false
|
||||
|
||||
private lateinit var cipherDatabaseAction : CipherDatabaseAction
|
||||
|
||||
private var cipherDatabaseListener: CipherDatabaseAction.CipherDatabaseListener? = null
|
||||
|
||||
private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by activityViewModels()
|
||||
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
private var mAllowAdvancedUnlockMenu = false
|
||||
private var mAddBiometricMenuInProgress = false
|
||||
|
||||
// Only keep connection when we request a device credential activity
|
||||
private var keepConnection = false
|
||||
|
||||
private var mDeviceCredentialResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false
|
||||
// To wait resume
|
||||
if (keepConnection) {
|
||||
mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded =
|
||||
result.resultCode == Activity.RESULT_OK
|
||||
}
|
||||
keepConnection = false
|
||||
}
|
||||
|
||||
private val menuProvider: MenuProvider = object: MenuProvider {
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// biometric menu
|
||||
if (mAllowAdvancedUnlockMenu)
|
||||
menuInflater.inflate(R.menu.advanced_unlock, menu)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
deleteEncryptedDatabaseKey()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context)
|
||||
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mBuilderListener = context as BuilderListener
|
||||
}
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + BuilderListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
|
||||
|
||||
mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) {
|
||||
initAdvancedUnlockMode()
|
||||
}
|
||||
|
||||
mAdvancedUnlockViewModel.onUnlockAvailabilityCheckRequested.observe(this) {
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
|
||||
mAdvancedUnlockViewModel.onDatabaseFileLoaded.observe(this) {
|
||||
onDatabaseLoaded(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
val rootView = inflater.inflate(R.layout.fragment_advanced_unlock, container, false)
|
||||
|
||||
mAdvancedUnlockInfoView = rootView.findViewById(R.id.advanced_unlock_view)
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
context?.let {
|
||||
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(it)
|
||||
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(it)
|
||||
}
|
||||
keepConnection = false
|
||||
}
|
||||
|
||||
private fun onDatabaseLoaded(databaseUri: Uri?) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// To get device credential unlock result, only if same database uri
|
||||
if (databaseUri != null
|
||||
&& mAdvancedUnlockEnabled) {
|
||||
val deviceCredentialAuthSucceeded = mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded
|
||||
deviceCredentialAuthSucceeded?.let {
|
||||
if (databaseUri == databaseFileUri) {
|
||||
if (deviceCredentialAuthSucceeded == true) {
|
||||
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationSucceeded()
|
||||
} else {
|
||||
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationFailed()
|
||||
}
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
} ?: run {
|
||||
if (databaseUri != databaseFileUri) {
|
||||
connect(databaseUri)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check unlock availability and change the current mode depending of device's state
|
||||
*/
|
||||
private fun checkUnlockAvailability() {
|
||||
context?.let { context ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
allowOpenBiometricPrompt = true
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(context)) {
|
||||
// biometric not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(context)
|
||||
if (!PreferencesUtil.isAdvancedUnlockEnable(context)
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
|
||||
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
|
||||
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
|
||||
} else {
|
||||
// biometric is available but not configured, show icon but in disabled state with some information
|
||||
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
|
||||
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
|
||||
} else {
|
||||
selectMode()
|
||||
}
|
||||
}
|
||||
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
|
||||
if (AdvancedUnlockManager.isDeviceSecure(context)) {
|
||||
selectMode()
|
||||
} else {
|
||||
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun selectMode() {
|
||||
// Check if fingerprint well init (be called the first time the fingerprint is configured
|
||||
// and the activity still active)
|
||||
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
|
||||
advancedUnlockManager = AdvancedUnlockManager { requireActivity() }
|
||||
// callback for fingerprint findings
|
||||
advancedUnlockManager?.advancedUnlockCallback = this
|
||||
}
|
||||
// Recheck to change the mode
|
||||
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
|
||||
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
} else {
|
||||
if (mBuilderListener?.conditionToStoreCredential() == true) {
|
||||
// listen for encryption
|
||||
toggleMode(Mode.STORE_CREDENTIAL)
|
||||
} else {
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
|
||||
// biometric available but no stored password found yet for this DB so show info don't listen
|
||||
toggleMode(if (containsCipher) {
|
||||
// listen for decryption
|
||||
Mode.EXTRACT_CREDENTIAL
|
||||
} else {
|
||||
// wait for typing
|
||||
Mode.WAIT_CREDENTIAL
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun toggleMode(newBiometricMode: Mode) {
|
||||
if (newBiometricMode != biometricMode) {
|
||||
biometricMode = newBiometricMode
|
||||
initAdvancedUnlockMode()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initNotAvailable() {
|
||||
showViews(false)
|
||||
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(null)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun openBiometricSetting() {
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener {
|
||||
try {
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
context?.startActivity(Intent(Settings.ACTION_BIOMETRIC_ENROLL))
|
||||
}
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> {
|
||||
@Suppress("DEPRECATION") context
|
||||
?.startActivity(Intent(Settings.ACTION_FINGERPRINT_ENROLL))
|
||||
}
|
||||
else -> {
|
||||
context?.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
|
||||
context?.startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initSecurityUpdateRequired() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
|
||||
|
||||
openBiometricSetting()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initNotConfigured() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.configure_biometric)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
openBiometricSetting()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initKeyManagerNotAvailable() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
|
||||
|
||||
openBiometricSetting()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initWaitData() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.unavailable)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
context?.let { context ->
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener {
|
||||
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
|
||||
context.getString(R.string.credential_before_click_advanced_unlock_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
if (allowOpenBiometricPrompt) {
|
||||
if (cryptoPrompt.isDeviceCredentialOperation)
|
||||
keepConnection = true
|
||||
try {
|
||||
advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt,
|
||||
mDeviceCredentialResultLauncher)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to open advanced unlock prompt", e)
|
||||
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initEncryptData() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.unlock_and_link_biometric)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockManager?.initEncryptData { cryptoPrompt ->
|
||||
// Set listener to open the biometric dialog and save credential
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
}
|
||||
} ?: throw Exception("AdvancedUnlockManager not initialized")
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initDecryptData() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.unlock)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockManager?.let { unlockHelper ->
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
|
||||
cipherDatabase?.let {
|
||||
unlockHelper.initDecryptData(it.specParameters) { cryptoPrompt ->
|
||||
|
||||
// Set listener to open the biometric dialog and check credential
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
}
|
||||
|
||||
// Auto open the biometric prompt
|
||||
if (mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt
|
||||
&& mAutoOpenPromptEnabled) {
|
||||
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false
|
||||
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
}
|
||||
}
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: throw UnknownDatabaseLocationException()
|
||||
} ?: throw Exception("AdvancedUnlockManager not initialized")
|
||||
}
|
||||
|
||||
private fun initAdvancedUnlockMode() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mAllowAdvancedUnlockMenu = false
|
||||
try {
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
|
||||
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
|
||||
Mode.WAIT_CREDENTIAL -> initWaitData()
|
||||
Mode.STORE_CREDENTIAL -> initEncryptData()
|
||||
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
onGenericException(e)
|
||||
}
|
||||
invalidateBiometricMenu()
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidateBiometricMenu() {
|
||||
// Show fingerprint key deletion
|
||||
if (!mAddBiometricMenuInProgress) {
|
||||
mAddBiometricMenuInProgress = true
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
|
||||
mAllowAdvancedUnlockMenu = containsCipher
|
||||
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
|
||||
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
mAddBiometricMenuInProgress = false
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun connect(databaseUri: Uri) {
|
||||
showViews(true)
|
||||
this.databaseFileUri = databaseUri
|
||||
cipherDatabaseListener = object: CipherDatabaseAction.CipherDatabaseListener {
|
||||
override fun onCipherDatabaseCleared() {
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
}
|
||||
cipherDatabaseAction.apply {
|
||||
reloadPreferences()
|
||||
cipherDatabaseListener?.let {
|
||||
registerDatabaseListener(it)
|
||||
}
|
||||
}
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun disconnect(hideViews: Boolean = true,
|
||||
closePrompt: Boolean = true) {
|
||||
this.databaseFileUri = null
|
||||
// Close the biometric prompt
|
||||
allowOpenBiometricPrompt = false
|
||||
if (closePrompt)
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
cipherDatabaseListener?.let {
|
||||
cipherDatabaseAction.unregisterDatabaseListener(it)
|
||||
}
|
||||
biometricMode = Mode.BIOMETRIC_UNAVAILABLE
|
||||
if (hideViews) {
|
||||
showViews(false)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun deleteEncryptedDatabaseKey() {
|
||||
mAllowAdvancedUnlockMenu = false
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
} ?: checkUnlockAvailability()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||
setAdvancedUnlockedMessageView(errString.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onAuthenticationFailed() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
||||
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onAuthenticationSucceeded() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> {
|
||||
}
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
|
||||
}
|
||||
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> {
|
||||
}
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> {
|
||||
}
|
||||
Mode.WAIT_CREDENTIAL -> {
|
||||
}
|
||||
Mode.STORE_CREDENTIAL -> {
|
||||
// newly store the entered password in encrypted way
|
||||
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
|
||||
advancedUnlockManager?.encryptData(credential)
|
||||
}
|
||||
}
|
||||
Mode.EXTRACT_CREDENTIAL -> {
|
||||
// retrieve the encrypted value from preferences
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
|
||||
cipherDatabase?.encryptedValue?.let { value ->
|
||||
advancedUnlockManager?.decryptData(value)
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: run {
|
||||
onAuthenticationError(-1, getString(R.string.error_database_uri_null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
mBuilderListener?.onCredentialEncrypted(
|
||||
CipherEncryptDatabase().apply {
|
||||
this.databaseUri = databaseUri
|
||||
this.credentialStorage = credentialDatabaseStorage
|
||||
this.encryptedValue = encryptedValue
|
||||
this.specParameters = ivSpec
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleDecryptedResult(decryptedValue: ByteArray) {
|
||||
// Load database directly with password retrieve
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
mBuilderListener?.onCredentialDecrypted(
|
||||
CipherDecryptDatabase().apply {
|
||||
this.databaseUri = databaseUri
|
||||
this.credentialStorage = credentialDatabaseStorage
|
||||
this.decryptedValue = decryptedValue
|
||||
}
|
||||
)
|
||||
cipherDatabaseAction.resetCipherParameters(databaseUri)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onUnrecoverableKeyException(e: Exception) {
|
||||
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onGenericException(e: Exception) {
|
||||
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
|
||||
setAdvancedUnlockedMessageView(errorMessage)
|
||||
}
|
||||
|
||||
private fun showViews(show: Boolean) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
if (show) {
|
||||
if (mAdvancedUnlockInfoView?.visibility != View.VISIBLE)
|
||||
mAdvancedUnlockInfoView?.showByFading()
|
||||
}
|
||||
else {
|
||||
if (mAdvancedUnlockInfoView?.visibility == View.VISIBLE)
|
||||
mAdvancedUnlockInfoView?.hideByFading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun setAdvancedUnlockedTitleView(textId: Int) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
mAdvancedUnlockInfoView?.setTitle(textId)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun setAdvancedUnlockedMessageView(textId: Int) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
mAdvancedUnlockInfoView?.setMessage(textId)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
mAdvancedUnlockInfoView?.setMessage(text)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
BIOMETRIC_UNAVAILABLE,
|
||||
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
|
||||
DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED,
|
||||
KEY_MANAGER_UNAVAILABLE,
|
||||
WAIT_CREDENTIAL,
|
||||
STORE_CREDENTIAL,
|
||||
EXTRACT_CREDENTIAL
|
||||
}
|
||||
|
||||
interface BuilderListener {
|
||||
fun retrieveCredentialForEncryption(): ByteArray
|
||||
fun conditionToStoreCredential(): Boolean
|
||||
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase)
|
||||
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (!keepConnection) {
|
||||
// If close prompt, bug "user not authenticated in Android R"
|
||||
disconnect(false)
|
||||
advancedUnlockManager = null
|
||||
}
|
||||
}
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
mAdvancedUnlockInfoView = null
|
||||
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
disconnect()
|
||||
advancedUnlockManager = null
|
||||
mBuilderListener = null
|
||||
}
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mBuilderListener = null
|
||||
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = AdvancedUnlockFragment::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -1,506 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.*
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import java.security.KeyStore
|
||||
import java.security.UnrecoverableKeyException
|
||||
import java.util.concurrent.Executors
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity) {
|
||||
|
||||
private var keyStore: KeyStore? = null
|
||||
private var keyGenerator: KeyGenerator? = null
|
||||
private var cipher: Cipher? = null
|
||||
|
||||
private var biometricPrompt: BiometricPrompt? = null
|
||||
private var authenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
advancedUnlockCallback?.onAuthenticationSucceeded()
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
advancedUnlockCallback?.onAuthenticationFailed()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
advancedUnlockCallback?.onAuthenticationError(errorCode, errString)
|
||||
}
|
||||
}
|
||||
|
||||
var advancedUnlockCallback: AdvancedUnlockCallback? = null
|
||||
|
||||
private var isKeyManagerInit = false
|
||||
|
||||
private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(retrieveContext())
|
||||
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext())
|
||||
|
||||
val isKeyManagerInitialized: Boolean
|
||||
get() {
|
||||
if (!isKeyManagerInit) {
|
||||
advancedUnlockCallback?.onGenericException(Exception("Biometric not initialized"))
|
||||
}
|
||||
return isKeyManagerInit
|
||||
}
|
||||
|
||||
private fun isBiometricOperation(): Boolean {
|
||||
return biometricUnlockEnable || isDeviceCredentialBiometricOperation()
|
||||
}
|
||||
|
||||
// Since Android 30, device credential is also a biometric operation
|
||||
private fun isDeviceCredentialOperation(): Boolean {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||
&& deviceCredentialUnlockEnable
|
||||
}
|
||||
|
||||
private fun isDeviceCredentialBiometricOperation(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& deviceCredentialUnlockEnable
|
||||
}
|
||||
|
||||
init {
|
||||
if (isDeviceSecure(retrieveContext())
|
||||
&& (biometricUnlockEnable || deviceCredentialUnlockEnable)) {
|
||||
try {
|
||||
this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE)
|
||||
this.keyGenerator = KeyGenerator.getInstance(ADVANCED_UNLOCK_KEY_ALGORITHM, ADVANCED_UNLOCK_KEYSTORE)
|
||||
this.cipher = Cipher.getInstance(
|
||||
ADVANCED_UNLOCK_KEY_ALGORITHM + "/"
|
||||
+ ADVANCED_UNLOCK_BLOCKS_MODES + "/"
|
||||
+ ADVANCED_UNLOCK_ENCRYPTION_PADDING)
|
||||
isKeyManagerInit = (keyStore != null
|
||||
&& keyGenerator != null
|
||||
&& cipher != null)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize the keystore", e)
|
||||
isKeyManagerInit = false
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
} else {
|
||||
// really not much to do when no fingerprint support found
|
||||
isKeyManagerInit = false
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized private fun getSecretKey(): SecretKey? {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
// Create new key if needed
|
||||
keyStore?.let { keyStore ->
|
||||
keyStore.load(null)
|
||||
|
||||
try {
|
||||
if (!keyStore.containsAlias(ADVANCED_UNLOCK_KEYSTORE_KEY)) {
|
||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||
// and the constrains (purposes) in the constructor of the Builder
|
||||
keyGenerator?.init(
|
||||
KeyGenParameterSpec.Builder(
|
||||
ADVANCED_UNLOCK_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(ADVANCED_UNLOCK_BLOCKS_MODES)
|
||||
.setEncryptionPaddings(ADVANCED_UNLOCK_ENCRYPTION_PADDING)
|
||||
.apply {
|
||||
// Require the user to authenticate with a fingerprint to authorize every use
|
||||
// of the key, don't use it for device credential because it's the user authentication
|
||||
if (biometricUnlockEnable) {
|
||||
setUserAuthenticationRequired(true)
|
||||
}
|
||||
// To store in the security chip
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||
&& retrieveContext().packageManager.hasSystemFeature(
|
||||
PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
|
||||
setIsStrongBoxBacked(true)
|
||||
}
|
||||
}
|
||||
.build())
|
||||
keyGenerator?.generateKey()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to create a key in keystore", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
|
||||
return keyStore.getKey(ADVANCED_UNLOCK_KEYSTORE_KEY, null) as SecretKey?
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve the key in keystore", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Synchronized fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,) {
|
||||
initEncryptData(actionIfCypherInit, true)
|
||||
}
|
||||
|
||||
@Synchronized private fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
|
||||
firstLaunch: Boolean) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.let { cipher ->
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
|
||||
actionIfCypherInit.invoke(
|
||||
AdvancedUnlockCryptoPrompt(
|
||||
cipher,
|
||||
R.string.advanced_unlock_prompt_store_credential_title,
|
||||
R.string.advanced_unlock_prompt_store_credential_message,
|
||||
isDeviceCredentialOperation(), isBiometricOperation())
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
||||
advancedUnlockCallback?.onUnrecoverableKeyException(unrecoverableKeyException)
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
|
||||
if (firstLaunch) {
|
||||
deleteAllEntryKeysInKeystoreForBiometric(retrieveContext())
|
||||
initEncryptData(actionIfCypherInit, false)
|
||||
} else {
|
||||
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun encryptData(value: ByteArray) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val encrypted = cipher?.doFinal(value) ?: byteArrayOf()
|
||||
// passes updated iv spec on to callback so this can be stored for decryption
|
||||
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
|
||||
advancedUnlockCallback?.handleEncryptedResult(encrypted, spec.iv)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to encrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun initDecryptData(ivSpecValue: ByteArray,
|
||||
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
|
||||
initDecryptData(ivSpecValue, actionIfCypherInit, true)
|
||||
}
|
||||
|
||||
@Synchronized private fun initDecryptData(ivSpecValue: ByteArray,
|
||||
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
|
||||
firstLaunch: Boolean = true) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// important to restore spec here that was used for decryption
|
||||
val spec = IvParameterSpec(ivSpecValue)
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.let { cipher ->
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
||||
|
||||
actionIfCypherInit.invoke(
|
||||
AdvancedUnlockCryptoPrompt(
|
||||
cipher,
|
||||
R.string.advanced_unlock_prompt_extract_credential_title,
|
||||
null,
|
||||
isDeviceCredentialOperation(), isBiometricOperation())
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
|
||||
if (firstLaunch) {
|
||||
deleteKeystoreKey()
|
||||
initDecryptData(ivSpecValue, actionIfCypherInit, firstLaunch)
|
||||
} else {
|
||||
advancedUnlockCallback?.onUnrecoverableKeyException(unrecoverableKeyException)
|
||||
}
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
|
||||
if (firstLaunch) {
|
||||
deleteAllEntryKeysInKeystoreForBiometric(retrieveContext())
|
||||
initDecryptData(ivSpecValue, actionIfCypherInit, firstLaunch)
|
||||
} else {
|
||||
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun decryptData(encryptedValue: ByteArray) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// actual decryption here
|
||||
cipher?.doFinal(encryptedValue)?.let { decrypted ->
|
||||
advancedUnlockCallback?.handleDecryptedResult(decrypted)
|
||||
}
|
||||
} catch (badPaddingException: BadPaddingException) {
|
||||
Log.e(TAG, "Unable to decrypt data", badPaddingException)
|
||||
advancedUnlockCallback?.onInvalidKeyException(badPaddingException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to decrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun deleteKeystoreKey() {
|
||||
try {
|
||||
keyStore?.load(null)
|
||||
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to delete entry key in keystore", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt,
|
||||
deviceCredentialResultLauncher: ActivityResultLauncher<Intent>
|
||||
) {
|
||||
// Init advanced unlock prompt
|
||||
if (biometricPrompt == null) {
|
||||
biometricPrompt = BiometricPrompt(retrieveContext(),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
authenticationCallback)
|
||||
}
|
||||
|
||||
val promptTitle = retrieveContext().getString(cryptoPrompt.promptTitleId)
|
||||
val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId ->
|
||||
retrieveContext().getString(descriptionId)
|
||||
} ?: ""
|
||||
|
||||
if (cryptoPrompt.isBiometricOperation) {
|
||||
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(promptTitle)
|
||||
if (promptDescription.isNotEmpty())
|
||||
setDescription(promptDescription)
|
||||
setConfirmationRequired(false)
|
||||
if (isDeviceCredentialBiometricOperation()) {
|
||||
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
||||
} else {
|
||||
setNegativeButtonText(retrieveContext().getString(android.R.string.cancel))
|
||||
}
|
||||
}.build()
|
||||
biometricPrompt?.authenticate(
|
||||
promptInfoExtractCredential,
|
||||
BiometricPrompt.CryptoObject(cryptoPrompt.cipher))
|
||||
}
|
||||
else if (cryptoPrompt.isDeviceCredentialOperation) {
|
||||
val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java)
|
||||
@Suppress("DEPRECATION")
|
||||
deviceCredentialResultLauncher.launch(
|
||||
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun closeBiometricPrompt() {
|
||||
biometricPrompt?.cancelAuthentication()
|
||||
}
|
||||
|
||||
interface AdvancedUnlockErrorCallback {
|
||||
fun onUnrecoverableKeyException(e: Exception)
|
||||
fun onInvalidKeyException(e: Exception)
|
||||
fun onGenericException(e: Exception)
|
||||
}
|
||||
|
||||
interface AdvancedUnlockCallback : AdvancedUnlockErrorCallback {
|
||||
fun onAuthenticationSucceeded()
|
||||
fun onAuthenticationFailed()
|
||||
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
|
||||
fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray)
|
||||
fun handleDecryptedResult(decryptedValue: ByteArray)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = AdvancedUnlockManager::class.java.name
|
||||
|
||||
private const val ADVANCED_UNLOCK_KEYSTORE = "AndroidKeyStore"
|
||||
private const val ADVANCED_UNLOCK_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
|
||||
private const val ADVANCED_UNLOCK_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
||||
private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
|
||||
private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun canAuthenticate(context: Context): Int {
|
||||
return try {
|
||||
BiometricManager.from(context).canAuthenticate(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
|
||||
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
||||
} else {
|
||||
BIOMETRIC_STRONG
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with strong biometric.", e)
|
||||
try {
|
||||
BiometricManager.from(context).canAuthenticate(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
|
||||
BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
} else {
|
||||
BIOMETRIC_WEAK
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with weak biometric.", e)
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isDeviceSecure(context: Context): Boolean {
|
||||
return ContextCompat.getSystemService(context, KeyguardManager::class.java)
|
||||
?.isDeviceSecure ?: false
|
||||
}
|
||||
|
||||
fun biometricUnlockSupported(context: Context): Boolean {
|
||||
val biometricCanAuthenticate = try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with strong biometric.", e)
|
||||
try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with weak biometric.", e)
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
}
|
||||
}
|
||||
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
)
|
||||
}
|
||||
|
||||
fun deviceCredentialUnlockSupported(context: Context): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
|
||||
(biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry key in keystore
|
||||
*/
|
||||
fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity,
|
||||
advancedCallback: AdvancedUnlockErrorCallback) {
|
||||
AdvancedUnlockManager{ fragmentActivity }.apply {
|
||||
advancedUnlockCallback = object : AdvancedUnlockCallback {
|
||||
override fun onAuthenticationSucceeded() {}
|
||||
|
||||
override fun onAuthenticationFailed() {}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {}
|
||||
|
||||
override fun handleDecryptedResult(decryptedValue: ByteArray) {}
|
||||
|
||||
override fun onUnrecoverableKeyException(e: Exception) {
|
||||
advancedCallback.onUnrecoverableKeyException(e)
|
||||
}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
advancedCallback.onInvalidKeyException(e)
|
||||
}
|
||||
|
||||
override fun onGenericException(e: Exception) {
|
||||
advancedCallback.onGenericException(e)
|
||||
}
|
||||
}
|
||||
deleteKeystoreKey()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAllEntryKeysInKeystoreForBiometric(activity: FragmentActivity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
deleteEntryKeyInKeystoreForBiometric(
|
||||
activity,
|
||||
object : AdvancedUnlockErrorCallback {
|
||||
fun showException(e: Exception) {
|
||||
Toast.makeText(activity,
|
||||
activity.getString(R.string.advanced_unlock_scanning_error, e.localizedMessage),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onUnrecoverableKeyException(e: Exception) {
|
||||
showException(e)
|
||||
}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
showException(e)
|
||||
}
|
||||
|
||||
override fun onGenericException(e: Exception) {
|
||||
showException(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
CipherDatabaseAction.getInstance(activity.applicationContext).deleteAll()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import javax.crypto.Cipher
|
||||
|
||||
data class DeviceUnlockCryptoPrompt(
|
||||
var type: DeviceUnlockCryptoPromptType,
|
||||
var cipher: Cipher,
|
||||
@StringRes var titleId: Int,
|
||||
@StringRes var descriptionId: Int? = null,
|
||||
var isDeviceCredentialOperation: Boolean,
|
||||
var isBiometricOperation: Boolean
|
||||
)
|
||||
|
||||
enum class DeviceUnlockCryptoPromptType {
|
||||
CREDENTIAL_ENCRYPTION, CREDENTIAL_DECRYPTION
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity.Companion.UI_VISIBLE_DURING_LOCK
|
||||
import com.kunzisoft.keepass.view.DeviceUnlockView
|
||||
import com.kunzisoft.keepass.view.hideByFading
|
||||
import com.kunzisoft.keepass.view.showByFading
|
||||
import com.kunzisoft.keepass.viewmodels.DeviceUnlockPromptMode
|
||||
import com.kunzisoft.keepass.viewmodels.DeviceUnlockViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
class DeviceUnlockFragment: Fragment() {
|
||||
|
||||
private var mDeviceUnlockView: DeviceUnlockView? = null
|
||||
|
||||
private val mDeviceUnlockViewModel: DeviceUnlockViewModel by activityViewModels()
|
||||
|
||||
private var mBiometricPrompt: BiometricPrompt? = null
|
||||
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
private var mAllowAdvancedUnlockMenu = false
|
||||
|
||||
private var mDeviceCredentialResultLauncher: ActivityResultLauncher<Intent>? = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
mDeviceUnlockViewModel.onAuthenticationSucceeded(result)
|
||||
} else {
|
||||
setAuthenticationFailed()
|
||||
}
|
||||
}
|
||||
|
||||
private var biometricAuthenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
mDeviceUnlockViewModel.onAuthenticationSucceeded(result)
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
setAuthenticationFailed()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
setAuthenticationError(errorCode, errString)
|
||||
}
|
||||
}
|
||||
|
||||
private val menuProvider: MenuProvider = object: MenuProvider {
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
// biometric menu
|
||||
if (mAllowAdvancedUnlockMenu)
|
||||
menuInflater.inflate(R.menu.advanced_unlock, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.menu_keystore_remove_key ->
|
||||
deleteEncryptedDatabaseKey()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
val rootView = inflater.inflate(R.layout.fragment_advanced_unlock, container, false)
|
||||
|
||||
mDeviceUnlockView = rootView.findViewById(R.id.advanced_unlock_view)
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Init device unlock prompt
|
||||
mBiometricPrompt = BiometricPrompt(
|
||||
this@DeviceUnlockFragment,
|
||||
Executors.newSingleThreadExecutor(),
|
||||
biometricAuthenticationCallback
|
||||
)
|
||||
|
||||
activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mDeviceUnlockViewModel.uiState.collect { uiState ->
|
||||
// Change mode
|
||||
toggleDeviceCredentialMode(uiState.newDeviceUnlockMode)
|
||||
// Prompt
|
||||
manageDeviceCredentialPrompt(uiState.cryptoPromptState)
|
||||
// Advanced menu
|
||||
mAllowAdvancedUnlockMenu = uiState.allowAdvancedUnlockMenu
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Don't allow auto open prompt if lock become when UI visible
|
||||
if (UI_VISIBLE_DURING_LOCK) {
|
||||
mDeviceUnlockViewModel.allowAutoOpenBiometricPrompt = false
|
||||
}
|
||||
|
||||
mDeviceUnlockViewModel.checkUnlockAvailability()
|
||||
}
|
||||
|
||||
fun cancelBiometricPrompt() {
|
||||
mBiometricPrompt?.cancelAuthentication()
|
||||
}
|
||||
|
||||
private fun toggleDeviceCredentialMode(deviceUnlockMode: DeviceUnlockMode) {
|
||||
try {
|
||||
when (deviceUnlockMode) {
|
||||
DeviceUnlockMode.BIOMETRIC_UNAVAILABLE -> setNotAvailableMode()
|
||||
DeviceUnlockMode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> setSecurityUpdateRequiredMode()
|
||||
DeviceUnlockMode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> setNotConfiguredMode()
|
||||
DeviceUnlockMode.KEY_MANAGER_UNAVAILABLE -> setKeyManagerNotAvailableMode()
|
||||
DeviceUnlockMode.WAIT_CREDENTIAL -> setWaitCredentialMode()
|
||||
DeviceUnlockMode.STORE_CREDENTIAL -> setStoreCredentialMode()
|
||||
DeviceUnlockMode.EXTRACT_CREDENTIAL -> setExtractCredentialMode()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
mDeviceUnlockViewModel.setException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun manageDeviceCredentialPrompt(
|
||||
state: DeviceUnlockPromptMode
|
||||
) {
|
||||
mDeviceUnlockViewModel.cryptoPrompt?.let { prompt ->
|
||||
when (state) {
|
||||
DeviceUnlockPromptMode.IDLE -> {}
|
||||
DeviceUnlockPromptMode.SHOW -> {
|
||||
openPrompt(prompt)
|
||||
mDeviceUnlockViewModel.promptShown()
|
||||
}
|
||||
DeviceUnlockPromptMode.CLOSE -> {
|
||||
cancelBiometricPrompt()
|
||||
mDeviceUnlockViewModel.biometricPromptClosed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPrompt(cryptoPrompt: DeviceUnlockCryptoPrompt) {
|
||||
try {
|
||||
val promptTitle = getString(cryptoPrompt.titleId)
|
||||
val promptDescription = cryptoPrompt.descriptionId?.let { descriptionId ->
|
||||
getString(descriptionId)
|
||||
} ?: ""
|
||||
|
||||
if (cryptoPrompt.isBiometricOperation) {
|
||||
mBiometricPrompt?.authenticate(
|
||||
BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(promptTitle)
|
||||
if (promptDescription.isNotEmpty())
|
||||
setDescription(promptDescription)
|
||||
setConfirmationRequired(false)
|
||||
if (isDeviceCredentialBiometricOperation(context)) {
|
||||
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
||||
} else {
|
||||
setNegativeButtonText(getString(android.R.string.cancel))
|
||||
}
|
||||
}.build(),
|
||||
BiometricPrompt.CryptoObject(cryptoPrompt.cipher))
|
||||
} else if (cryptoPrompt.isDeviceCredentialOperation) {
|
||||
context?.let { context ->
|
||||
@Suppress("DEPRECATION")
|
||||
mDeviceCredentialResultLauncher?.launch(
|
||||
ContextCompat.getSystemService(
|
||||
context,
|
||||
KeyguardManager::class.java
|
||||
)?.createConfirmDeviceCredentialIntent(
|
||||
promptTitle,
|
||||
promptDescription
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to open prompt", e)
|
||||
mDeviceUnlockViewModel.setException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNotAvailableMode() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
showViews(false)
|
||||
mDeviceUnlockView?.setDeviceUnlockButtonViewClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openBiometricSetting() {
|
||||
mDeviceUnlockView?.setDeviceUnlockButtonViewClickListener {
|
||||
try {
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
context?.startActivity(Intent(Settings.ACTION_BIOMETRIC_ENROLL))
|
||||
}
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> {
|
||||
@Suppress("DEPRECATION") context
|
||||
?.startActivity(Intent(Settings.ACTION_FINGERPRINT_ENROLL))
|
||||
}
|
||||
else -> {
|
||||
context?.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
|
||||
context?.startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSecurityUpdateRequiredMode() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
|
||||
openBiometricSetting()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNotConfiguredMode() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.configure_biometric)
|
||||
openBiometricSetting()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setKeyManagerNotAvailableMode() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
|
||||
openBiometricSetting()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setWaitCredentialMode() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.unavailable)
|
||||
context?.let { context ->
|
||||
mDeviceUnlockView?.setDeviceUnlockButtonViewClickListener {
|
||||
mDeviceUnlockViewModel.setException(SecurityException(
|
||||
context.getString(R.string.credential_before_click_advanced_unlock_button)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setStoreCredentialMode() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.unlock_and_link_biometric)
|
||||
context?.let { context ->
|
||||
mDeviceUnlockView?.setDeviceUnlockButtonViewClickListener { view ->
|
||||
mDeviceUnlockViewModel.showPrompt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setExtractCredentialMode() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.unlock)
|
||||
context?.let { context ->
|
||||
mDeviceUnlockView?.setDeviceUnlockButtonViewClickListener { view ->
|
||||
mDeviceUnlockViewModel.showPrompt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteEncryptedDatabaseKey() {
|
||||
mDeviceUnlockViewModel.deleteEncryptedDatabaseKey()
|
||||
}
|
||||
|
||||
private fun showViews(show: Boolean) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
if (show) {
|
||||
if (mDeviceUnlockView?.visibility != View.VISIBLE)
|
||||
mDeviceUnlockView?.showByFading()
|
||||
}
|
||||
else {
|
||||
if (mDeviceUnlockView?.visibility == View.VISIBLE)
|
||||
mDeviceUnlockView?.hideByFading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAdvancedUnlockedTitleView(textId: Int) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
mDeviceUnlockView?.setTitle(textId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||
when (errorCode) {
|
||||
BiometricPrompt.ERROR_CANCELED,
|
||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
|
||||
BiometricPrompt.ERROR_USER_CANCELED -> {
|
||||
// Ignore negative button
|
||||
}
|
||||
else ->
|
||||
mDeviceUnlockViewModel.setException(SecurityException(errString.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAuthenticationFailed() {
|
||||
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
||||
mDeviceUnlockViewModel.setException(
|
||||
SecurityException(getString(R.string.advanced_unlock_not_recognized))
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
mDeviceUnlockView = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mDeviceUnlockViewModel.disconnect()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = DeviceUnlockFragment::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import java.security.KeyStore
|
||||
import java.security.UnrecoverableKeyException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class DeviceUnlockManager(private var appContext: Context) {
|
||||
|
||||
private var keyStore: KeyStore? = null
|
||||
private var keyGenerator: KeyGenerator? = null
|
||||
private var cipher: Cipher? = null
|
||||
|
||||
private var biometricUnlockEnable = isBiometricUnlockEnable(appContext)
|
||||
private var deviceCredentialUnlockEnable = isDeviceCredentialUnlockEnable(appContext)
|
||||
|
||||
init {
|
||||
if (biometricUnlockEnable || deviceCredentialUnlockEnable) {
|
||||
if (isDeviceSecure(appContext)) {
|
||||
try {
|
||||
this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE)
|
||||
this.keyGenerator = KeyGenerator.getInstance(
|
||||
ADVANCED_UNLOCK_KEY_ALGORITHM,
|
||||
ADVANCED_UNLOCK_KEYSTORE
|
||||
)
|
||||
this.cipher = Cipher.getInstance(
|
||||
ADVANCED_UNLOCK_KEY_ALGORITHM + "/"
|
||||
+ ADVANCED_UNLOCK_BLOCKS_MODES + "/"
|
||||
+ ADVANCED_UNLOCK_ENCRYPTION_PADDING
|
||||
)
|
||||
if (keyStore == null) {
|
||||
throw SecurityException("Unable to initialize the keystore")
|
||||
}
|
||||
if (keyGenerator == null) {
|
||||
throw SecurityException("Unable to initialize the key generator")
|
||||
}
|
||||
if (cipher == null) {
|
||||
throw SecurityException("Unable to initialize the cipher")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize the device unlock manager", e)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
throw SecurityException("Device not secure enough")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized private fun getSecretKey(): SecretKey? {
|
||||
try {
|
||||
// Create new key if needed
|
||||
keyStore?.let { keyStore ->
|
||||
keyStore.load(null)
|
||||
try {
|
||||
if (!keyStore.containsAlias(ADVANCED_UNLOCK_KEYSTORE_KEY)) {
|
||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||
// and the constrains (purposes) in the constructor of the Builder
|
||||
keyGenerator?.init(
|
||||
KeyGenParameterSpec.Builder(
|
||||
ADVANCED_UNLOCK_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(ADVANCED_UNLOCK_BLOCKS_MODES)
|
||||
.setEncryptionPaddings(ADVANCED_UNLOCK_ENCRYPTION_PADDING)
|
||||
.apply {
|
||||
// Require the user to authenticate with a fingerprint to authorize every use
|
||||
// of the key, don't use it for device credential because it's the user authentication
|
||||
if (biometricUnlockEnable) {
|
||||
setUserAuthenticationRequired(true)
|
||||
}
|
||||
// To store in the security chip
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||
&& appContext.packageManager.hasSystemFeature(
|
||||
PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
|
||||
setIsStrongBoxBacked(true)
|
||||
}
|
||||
}
|
||||
.build())
|
||||
keyGenerator?.generateKey()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to create a key in keystore", e)
|
||||
throw e
|
||||
}
|
||||
return keyStore.getKey(ADVANCED_UNLOCK_KEYSTORE_KEY, null) as SecretKey?
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve the key in keystore", e)
|
||||
throw e
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Synchronized fun initEncryptData(
|
||||
actionIfCypherInit: (cryptoPrompt: DeviceUnlockCryptoPrompt) -> Unit
|
||||
) {
|
||||
initEncryptData(true, actionIfCypherInit)
|
||||
}
|
||||
|
||||
@Synchronized private fun initEncryptData(
|
||||
firstLaunch: Boolean,
|
||||
actionIfCypherInit: (cryptoPrompt: DeviceUnlockCryptoPrompt) -> Unit
|
||||
) {
|
||||
try {
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.let { cipher ->
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
actionIfCypherInit.invoke(
|
||||
DeviceUnlockCryptoPrompt(
|
||||
type = DeviceUnlockCryptoPromptType.CREDENTIAL_ENCRYPTION,
|
||||
cipher = cipher,
|
||||
titleId = R.string.advanced_unlock_prompt_store_credential_title,
|
||||
descriptionId = R.string.advanced_unlock_prompt_store_credential_message,
|
||||
isDeviceCredentialOperation = isDeviceCredentialOperation(
|
||||
deviceCredentialUnlockEnable
|
||||
),
|
||||
isBiometricOperation = isBiometricOperation(
|
||||
biometricUnlockEnable, deviceCredentialUnlockEnable
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
||||
throw unrecoverableKeyException
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
|
||||
if (firstLaunch) {
|
||||
deleteAllEntryKeysInKeystoreForBiometric(appContext)
|
||||
initEncryptData(false, actionIfCypherInit)
|
||||
} else {
|
||||
throw invalidKeyException
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun encryptData(
|
||||
value: ByteArray,
|
||||
cipher: Cipher?,
|
||||
handleEncryptedResult: (encryptedValue: ByteArray, ivSpec: ByteArray) -> Unit
|
||||
) {
|
||||
try {
|
||||
val encrypted = cipher?.doFinal(value) ?: byteArrayOf()
|
||||
// passes updated iv spec on to callback so this can be stored for decryption
|
||||
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
|
||||
handleEncryptedResult.invoke(encrypted, spec.iv)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to encrypt data", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun initDecryptData(
|
||||
ivSpecValue: ByteArray,
|
||||
actionIfCypherInit: (cryptoPrompt: DeviceUnlockCryptoPrompt) -> Unit
|
||||
) {
|
||||
initDecryptData(ivSpecValue, true, actionIfCypherInit)
|
||||
}
|
||||
|
||||
@Synchronized private fun initDecryptData(
|
||||
ivSpecValue: ByteArray,
|
||||
firstLaunch: Boolean = true,
|
||||
actionIfCypherInit: (cryptoPrompt: DeviceUnlockCryptoPrompt) -> Unit
|
||||
) {
|
||||
try {
|
||||
// important to restore spec here that was used for decryption
|
||||
val spec = IvParameterSpec(ivSpecValue)
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.let { cipher ->
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
||||
actionIfCypherInit.invoke(
|
||||
DeviceUnlockCryptoPrompt(
|
||||
type = DeviceUnlockCryptoPromptType.CREDENTIAL_DECRYPTION,
|
||||
cipher = cipher,
|
||||
titleId = R.string.advanced_unlock_prompt_extract_credential_title,
|
||||
descriptionId = null,
|
||||
isDeviceCredentialOperation = isDeviceCredentialOperation(
|
||||
deviceCredentialUnlockEnable
|
||||
),
|
||||
isBiometricOperation = isBiometricOperation(
|
||||
biometricUnlockEnable, deviceCredentialUnlockEnable
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
|
||||
if (firstLaunch) {
|
||||
deleteKeystoreKey()
|
||||
initDecryptData(ivSpecValue, false, actionIfCypherInit)
|
||||
} else {
|
||||
throw unrecoverableKeyException
|
||||
}
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
|
||||
if (firstLaunch) {
|
||||
deleteAllEntryKeysInKeystoreForBiometric(appContext)
|
||||
initDecryptData(ivSpecValue, false, actionIfCypherInit)
|
||||
} else {
|
||||
throw invalidKeyException
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun decryptData(
|
||||
encryptedValue: ByteArray,
|
||||
cipher: Cipher?,
|
||||
handleDecryptedResult: (decryptedValue: ByteArray) -> Unit
|
||||
) {
|
||||
try {
|
||||
// actual decryption here
|
||||
cipher?.doFinal(encryptedValue)?.let { decrypted ->
|
||||
handleDecryptedResult.invoke(decrypted)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to decrypt data", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun deleteKeystoreKey() {
|
||||
try {
|
||||
keyStore?.load(null)
|
||||
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to delete entry key in keystore", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = DeviceUnlockManager::class.java.name
|
||||
|
||||
private const val ADVANCED_UNLOCK_KEYSTORE = "AndroidKeyStore"
|
||||
private const val ADVANCED_UNLOCK_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
|
||||
private const val ADVANCED_UNLOCK_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
||||
private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
|
||||
private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun canAuthenticate(context: Context): Int {
|
||||
return try {
|
||||
BiometricManager.from(context).canAuthenticate(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
|
||||
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
||||
} else {
|
||||
BIOMETRIC_STRONG
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with strong biometric.", e)
|
||||
try {
|
||||
BiometricManager.from(context).canAuthenticate(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
|
||||
BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
} else {
|
||||
BIOMETRIC_WEAK
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with weak biometric.", e)
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isDeviceSecure(context: Context): Boolean {
|
||||
return ContextCompat.getSystemService(context, KeyguardManager::class.java)
|
||||
?.isDeviceSecure ?: false
|
||||
}
|
||||
|
||||
fun biometricUnlockSupported(context: Context): Boolean {
|
||||
val biometricCanAuthenticate = try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with strong biometric.", e)
|
||||
try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with weak biometric.", e)
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
}
|
||||
}
|
||||
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
)
|
||||
}
|
||||
|
||||
fun deviceCredentialUnlockSupported(context: Context): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
|
||||
(biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry key in keystore
|
||||
*/
|
||||
fun deleteEntryKeyInKeystoreForBiometric(
|
||||
appContext: Context
|
||||
) {
|
||||
DeviceUnlockManager(appContext).apply {
|
||||
deleteKeystoreKey()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAllEntryKeysInKeystoreForBiometric(appContext: Context) {
|
||||
try {
|
||||
deleteEntryKeyInKeystoreForBiometric(appContext)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(appContext,
|
||||
deviceUnlockError(e, appContext),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
} finally {
|
||||
CipherDatabaseAction.getInstance(appContext).deleteAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deviceUnlockError(error: Exception, context: Context): String {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& (error is UnrecoverableKeyException
|
||||
|| error is KeyPermanentlyInvalidatedException)) {
|
||||
context.getString(R.string.advanced_unlock_invalid_key)
|
||||
} else
|
||||
error.cause?.localizedMessage
|
||||
?: error.localizedMessage
|
||||
?: error.toString()
|
||||
}
|
||||
|
||||
fun isBiometricUnlockEnable(appContext: Context) =
|
||||
PreferencesUtil.isBiometricUnlockEnable(appContext)
|
||||
|
||||
fun isDeviceCredentialUnlockEnable(appContext: Context) =
|
||||
PreferencesUtil.isDeviceCredentialUnlockEnable(appContext)
|
||||
|
||||
private fun isBiometricOperation(
|
||||
biometricUnlockEnable: Boolean,
|
||||
deviceCredentialUnlockEnable: Boolean
|
||||
): Boolean {
|
||||
return biometricUnlockEnable
|
||||
|| isDeviceCredentialBiometricOperation(deviceCredentialUnlockEnable)
|
||||
}
|
||||
|
||||
// Since Android 30, device credential is also a biometric operation
|
||||
private fun isDeviceCredentialOperation(
|
||||
deviceCredentialUnlockEnable: Boolean
|
||||
): Boolean {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||
&& deviceCredentialUnlockEnable
|
||||
}
|
||||
|
||||
private fun isDeviceCredentialBiometricOperation(
|
||||
deviceCredentialUnlockEnable: Boolean
|
||||
): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& deviceCredentialUnlockEnable
|
||||
}
|
||||
|
||||
fun isDeviceCredentialBiometricOperation(context: Context?): Boolean {
|
||||
if (context == null) {
|
||||
return false
|
||||
}
|
||||
return isDeviceCredentialBiometricOperation(
|
||||
isDeviceCredentialUnlockEnable(context)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
enum class DeviceUnlockMode {
|
||||
BIOMETRIC_UNAVAILABLE,
|
||||
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
|
||||
DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED,
|
||||
KEY_MANAGER_UNAVAILABLE,
|
||||
WAIT_CREDENTIAL,
|
||||
STORE_CREDENTIAL,
|
||||
EXTRACT_CREDENTIAL
|
||||
}
|
||||
@@ -41,7 +41,7 @@ import com.kunzisoft.keepass.activities.dialogs.ProFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockManager
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||
@@ -251,7 +251,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
val tempAdvancedUnlockPreference: TwoStatePreference? = findPreference(getString(R.string.temp_advanced_unlock_enable_key))
|
||||
|
||||
val biometricUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
AdvancedUnlockManager.biometricUnlockSupported(activity)
|
||||
DeviceUnlockManager.biometricUnlockSupported(activity)
|
||||
} else false
|
||||
biometricUnlockEnablePreference?.apply {
|
||||
// False if under Marshmallow
|
||||
@@ -296,7 +296,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
|
||||
val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
AdvancedUnlockManager.deviceCredentialUnlockSupported(activity)
|
||||
DeviceUnlockManager.deviceCredentialUnlockSupported(activity)
|
||||
} else false
|
||||
deviceCredentialUnlockEnablePreference?.apply {
|
||||
// Biometric unlock already checked
|
||||
@@ -395,7 +395,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
validate?.invoke()
|
||||
warningAlertDialog?.setOnDismissListener(null)
|
||||
if (deleteKeys && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
AdvancedUnlockManager.deleteAllEntryKeysInKeystoreForBiometric(activity)
|
||||
DeviceUnlockManager.deleteAllEntryKeysInKeystoreForBiometric(activity)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.cancel)
|
||||
|
||||
@@ -29,7 +29,7 @@ import androidx.preference.PreferenceManager
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockManager
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
@@ -512,7 +512,7 @@ object PreferencesUtil {
|
||||
return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key),
|
||||
context.resources.getBoolean(R.bool.biometric_unlock_enable_default))
|
||||
&& (if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
AdvancedUnlockManager.biometricUnlockSupported(context)
|
||||
DeviceUnlockManager.biometricUnlockSupported(context)
|
||||
} else {
|
||||
false
|
||||
})
|
||||
|
||||
@@ -25,15 +25,14 @@ import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
class DeviceUnlockView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyle) {
|
||||
|
||||
private var biometricButtonView: Button? = null
|
||||
@@ -45,7 +44,7 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
|
||||
biometricButtonView = findViewById(R.id.biometric_button)
|
||||
}
|
||||
|
||||
fun setIconViewClickListener(listener: OnClickListener?) {
|
||||
fun setDeviceUnlockButtonViewClickListener(listener: OnClickListener?) {
|
||||
biometricButtonView?.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
@@ -60,14 +59,4 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
|
||||
fun setTitle(@StringRes textId: Int) {
|
||||
title = context.getString(textId)
|
||||
}
|
||||
|
||||
fun setMessage(text: CharSequence) {
|
||||
if (text.isNotEmpty())
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun setMessage(@StringRes textId: Int) {
|
||||
Toast.makeText(context, textId, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,9 +53,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
private var checkboxHardwareView: CompoundButton
|
||||
private var hardwareKeySelectionView: HardwareKeySelectionView
|
||||
|
||||
var onPasswordChecked: (CompoundButton.OnCheckedChangeListener)? = null
|
||||
var onKeyFileChecked: (CompoundButton.OnCheckedChangeListener)? = null
|
||||
var onHardwareKeyChecked: (CompoundButton.OnCheckedChangeListener)? = null
|
||||
var onConditionToStoreCredentialChanged: ((CredentialStorage, verified: Boolean) -> Unit)? = null
|
||||
var onValidateListener: (() -> Unit)? = null
|
||||
|
||||
private var mCredentialStorage: CredentialStorage = CredentialStorage.PASSWORD
|
||||
@@ -103,24 +101,33 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
handled
|
||||
}
|
||||
|
||||
checkboxPasswordView.setOnCheckedChangeListener { view, checked ->
|
||||
onPasswordChecked?.onCheckedChanged(view, checked)
|
||||
checkboxPasswordView.setOnCheckedChangeListener { _, _ ->
|
||||
onConditionToStoreCredentialChanged?.invoke(
|
||||
mCredentialStorage,
|
||||
conditionToStoreCredential()
|
||||
)
|
||||
}
|
||||
checkboxKeyFileView.setOnCheckedChangeListener { view, checked ->
|
||||
checkboxKeyFileView.setOnCheckedChangeListener { _, checked ->
|
||||
if (checked) {
|
||||
if (keyFileSelectionView.uri == null) {
|
||||
checkboxKeyFileView.isChecked = false
|
||||
}
|
||||
}
|
||||
onKeyFileChecked?.onCheckedChanged(view, checked)
|
||||
onConditionToStoreCredentialChanged?.invoke(
|
||||
mCredentialStorage,
|
||||
conditionToStoreCredential()
|
||||
)
|
||||
}
|
||||
checkboxHardwareView.setOnCheckedChangeListener { view, checked ->
|
||||
checkboxHardwareView.setOnCheckedChangeListener { _, checked ->
|
||||
if (checked) {
|
||||
if (hardwareKeySelectionView.hardwareKey == null) {
|
||||
checkboxHardwareView.isChecked = false
|
||||
}
|
||||
}
|
||||
onHardwareKeyChecked?.onCheckedChanged(view, checked)
|
||||
onConditionToStoreCredentialChanged?.invoke(
|
||||
mCredentialStorage,
|
||||
conditionToStoreCredential()
|
||||
)
|
||||
}
|
||||
|
||||
hardwareKeySelectionView.selectionListener = { _ ->
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class AdvancedUnlockViewModel : ViewModel() {
|
||||
|
||||
var allowAutoOpenBiometricPrompt : Boolean = true
|
||||
var deviceCredentialAuthSucceeded: Boolean? = null
|
||||
|
||||
val onInitAdvancedUnlockModeRequested : LiveData<Void?> get() = _onInitAdvancedUnlockModeRequested
|
||||
private val _onInitAdvancedUnlockModeRequested = SingleLiveEvent<Void?>()
|
||||
|
||||
val onUnlockAvailabilityCheckRequested : LiveData<Void?> get() = _onUnlockAvailabilityCheckRequested
|
||||
private val _onUnlockAvailabilityCheckRequested = SingleLiveEvent<Void?>()
|
||||
|
||||
val onDatabaseFileLoaded : LiveData<Uri?> get() = _onDatabaseFileLoaded
|
||||
private val _onDatabaseFileLoaded = SingleLiveEvent<Uri?>()
|
||||
|
||||
fun initAdvancedUnlockMode() {
|
||||
_onInitAdvancedUnlockModeRequested.call()
|
||||
}
|
||||
|
||||
fun checkUnlockAvailability() {
|
||||
_onUnlockAvailabilityCheckRequested.call()
|
||||
}
|
||||
|
||||
fun databaseFileLoaded(databaseUri: Uri?) {
|
||||
_onDatabaseFileLoaded.value = databaseUri
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockCryptoPrompt
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockCryptoPromptType
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockManager
|
||||
import com.kunzisoft.keepass.biometric.DeviceUnlockMode
|
||||
import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
|
||||
import com.kunzisoft.keepass.model.CipherDecryptDatabase
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import com.kunzisoft.keepass.model.CredentialStorage
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.crypto.Cipher
|
||||
|
||||
class DeviceUnlockViewModel(application: Application): AndroidViewModel(application) {
|
||||
|
||||
var allowAutoOpenBiometricPrompt : Boolean = true
|
||||
|
||||
private var cipherDatabaseListener: CipherDatabaseAction.CipherDatabaseListener? = null
|
||||
|
||||
private var isConditionToStoreCredentialVerified: Boolean = false
|
||||
|
||||
private var deviceUnlockManager: DeviceUnlockManager? = null
|
||||
private var databaseUri: Uri? = null
|
||||
|
||||
private var deviceUnlockMode = DeviceUnlockMode.BIOMETRIC_UNAVAILABLE
|
||||
var cryptoPrompt: DeviceUnlockCryptoPrompt? = null
|
||||
|
||||
// TODO Retrieve credential storage from app database
|
||||
var credentialDatabaseStorage: CredentialStorage = CredentialStorage.DEFAULT
|
||||
|
||||
val cipherDatabaseAction = CipherDatabaseAction.getInstance(getApplication())
|
||||
|
||||
private val _uiState = MutableStateFlow(DeviceUnlockState())
|
||||
val uiState: StateFlow<DeviceUnlockState> = _uiState
|
||||
|
||||
fun checkConditionToStoreCredential(condition: Boolean, databaseFileUri: Uri?) {
|
||||
isConditionToStoreCredentialVerified = condition
|
||||
checkUnlockAvailability(databaseFileUri)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check unlock availability by verifying device settings and database mode
|
||||
*/
|
||||
fun checkUnlockAvailability() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipherDatabase ->
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(getApplication())) {
|
||||
// biometric not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
val biometricCanAuthenticate = DeviceUnlockManager.canAuthenticate(getApplication())
|
||||
if (!PreferencesUtil.isAdvancedUnlockEnable(getApplication())
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||
changeMode(DeviceUnlockMode.BIOMETRIC_UNAVAILABLE)
|
||||
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
|
||||
changeMode(DeviceUnlockMode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
|
||||
} else {
|
||||
// biometric is available but not configured, show icon but in disabled state with some information
|
||||
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
|
||||
changeMode(DeviceUnlockMode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
|
||||
} else {
|
||||
selectMode(containsCipherDatabase)
|
||||
}
|
||||
}
|
||||
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(getApplication())) {
|
||||
if (DeviceUnlockManager.isDeviceSecure(getApplication())) {
|
||||
selectMode(containsCipherDatabase)
|
||||
} else {
|
||||
changeMode(DeviceUnlockMode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check unlock availability and change the current mode depending of device's state
|
||||
*/
|
||||
fun checkUnlockAvailability(databaseFileUri: Uri?) {
|
||||
databaseUri = databaseFileUri
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun selectMode(containsCipherDatabase: Boolean) {
|
||||
try {
|
||||
if (isConditionToStoreCredentialVerified) {
|
||||
deviceUnlockManager = DeviceUnlockManager(getApplication())
|
||||
// listen for encryption
|
||||
changeMode(DeviceUnlockMode.STORE_CREDENTIAL)
|
||||
initEncryptData()
|
||||
} else if (containsCipherDatabase) {
|
||||
deviceUnlockManager = DeviceUnlockManager(getApplication())
|
||||
// biometric available but no stored password found yet for this DB
|
||||
// listen for decryption
|
||||
changeMode(DeviceUnlockMode.EXTRACT_CREDENTIAL)
|
||||
initDecryptData()
|
||||
} else {
|
||||
// wait for typing
|
||||
changeMode(DeviceUnlockMode.WAIT_CREDENTIAL)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
changeMode(DeviceUnlockMode.KEY_MANAGER_UNAVAILABLE)
|
||||
setException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(databaseUri: Uri) {
|
||||
this.databaseUri = databaseUri
|
||||
cipherDatabaseListener = object: CipherDatabaseAction.CipherDatabaseListener {
|
||||
override fun onCipherDatabaseCleared() {
|
||||
closeBiometricPrompt()
|
||||
checkUnlockAvailability(databaseUri)
|
||||
}
|
||||
}
|
||||
cipherDatabaseAction.apply {
|
||||
reloadPreferences()
|
||||
cipherDatabaseListener?.let {
|
||||
registerDatabaseListener(it)
|
||||
}
|
||||
}
|
||||
checkUnlockAvailability(databaseUri)
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
this.databaseUri = null
|
||||
cipherDatabaseListener?.let {
|
||||
cipherDatabaseAction.unregisterDatabaseListener(it)
|
||||
}
|
||||
reset()
|
||||
}
|
||||
|
||||
fun databaseFileLoaded(databaseUri: Uri?) {
|
||||
// To get device credential unlock result, only if same database uri
|
||||
if (databaseUri != null
|
||||
&& PreferencesUtil.isAdvancedUnlockEnable(getApplication())) {
|
||||
if (databaseUri != this.databaseUri) {
|
||||
connect(databaseUri)
|
||||
}
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun onAuthenticationSucceeded(
|
||||
activityResult: ActivityResult
|
||||
) {
|
||||
cryptoPrompt?.let { prompt ->
|
||||
when (prompt.type) {
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_ENCRYPTION ->
|
||||
retrieveCredentialForEncryption( prompt.cipher)
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_DECRYPTION ->
|
||||
decryptCredential( prompt.cipher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun onAuthenticationSucceeded(
|
||||
result: BiometricPrompt.AuthenticationResult
|
||||
) {
|
||||
cryptoPrompt?.type?.let { type ->
|
||||
when (type) {
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_ENCRYPTION ->
|
||||
retrieveCredentialForEncryption(result.cryptoObject?.cipher)
|
||||
DeviceUnlockCryptoPromptType.CREDENTIAL_DECRYPTION ->
|
||||
decryptCredential(result.cryptoObject?.cipher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun retrieveCredentialForEncryption(cipher: Cipher?) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
credentialRequiredCipher = cipher
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun encryptCredential(
|
||||
credential: ByteArray,
|
||||
cipher: Cipher?
|
||||
) {
|
||||
try {
|
||||
deviceUnlockManager?.encryptData(
|
||||
value = credential,
|
||||
cipher = cipher,
|
||||
handleEncryptedResult = { encryptedValue, ivSpec ->
|
||||
databaseUri?.let { databaseUri ->
|
||||
onCredentialEncrypted(
|
||||
CipherEncryptDatabase().apply {
|
||||
this.databaseUri = databaseUri
|
||||
this.credentialStorage = credentialDatabaseStorage
|
||||
this.encryptedValue = encryptedValue
|
||||
this.specParameters = ivSpec
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
setException(e)
|
||||
} finally {
|
||||
// Reinit credential storage request
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
credentialRequiredCipher = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun decryptCredential(cipher: Cipher?) {
|
||||
// retrieve the encrypted value from preferences
|
||||
databaseUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
|
||||
cipherDatabase?.encryptedValue?.let { encryptedCredential ->
|
||||
try {
|
||||
deviceUnlockManager?.decryptData(
|
||||
encryptedValue = encryptedCredential,
|
||||
cipher = cipher,
|
||||
handleDecryptedResult = { decryptedValue ->
|
||||
// Load database directly with password retrieve
|
||||
onCredentialDecrypted(
|
||||
CipherDecryptDatabase().apply {
|
||||
this.databaseUri = databaseUri
|
||||
this.credentialStorage = credentialDatabaseStorage
|
||||
this.decryptedValue = decryptedValue
|
||||
}
|
||||
)
|
||||
cipherDatabaseAction.resetCipherParameters(databaseUri)
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
setException(e)
|
||||
}
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: run {
|
||||
setException(UnknownDatabaseLocationException())
|
||||
}
|
||||
}
|
||||
|
||||
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherEncryptDatabase = cipherEncryptDatabase
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeCredentialEncrypted() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherEncryptDatabase = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherDecryptDatabase = cipherDecryptDatabase
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeCredentialDecrypted() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherDecryptDatabase = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPromptRequested(
|
||||
cryptoPrompt: DeviceUnlockCryptoPrompt,
|
||||
autoOpen: Boolean = false
|
||||
) {
|
||||
this@DeviceUnlockViewModel.cryptoPrompt = cryptoPrompt
|
||||
if (autoOpen && PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(getApplication()))
|
||||
showPrompt()
|
||||
}
|
||||
|
||||
fun showPrompt() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cryptoPromptState = DeviceUnlockPromptMode.SHOW
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun promptShown() {
|
||||
allowAutoOpenBiometricPrompt = false
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cryptoPromptState = DeviceUnlockPromptMode.IDLE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setException(value: Exception?) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
exception = value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun exceptionShown() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
exception = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initEncryptData() {
|
||||
try {
|
||||
deviceUnlockManager?.initEncryptData { cryptoPrompt ->
|
||||
onPromptRequested(cryptoPrompt)
|
||||
} ?: setException(Exception("AdvancedUnlockManager not initialized"))
|
||||
} catch (e: Exception) {
|
||||
setException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initDecryptData() {
|
||||
databaseUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
|
||||
cipherDatabase?.let {
|
||||
try {
|
||||
deviceUnlockManager?.initDecryptData(cipherDatabase.specParameters) { cryptoPrompt ->
|
||||
onPromptRequested(cryptoPrompt, autoOpen = allowAutoOpenBiometricPrompt)
|
||||
} ?: setException(Exception("AdvancedUnlockManager not initialized"))
|
||||
} catch (e: Exception) {
|
||||
setException(e)
|
||||
}
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: setException(UnknownDatabaseLocationException())
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun changeMode(deviceUnlockMode: DeviceUnlockMode) {
|
||||
this.deviceUnlockMode = deviceUnlockMode
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
newDeviceUnlockMode = deviceUnlockMode,
|
||||
allowAdvancedUnlockMenu = containsCipher
|
||||
&& deviceUnlockMode != DeviceUnlockMode.BIOMETRIC_UNAVAILABLE
|
||||
&& deviceUnlockMode != DeviceUnlockMode.KEY_MANAGER_UNAVAILABLE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteEncryptedDatabaseKey() {
|
||||
closeBiometricPrompt()
|
||||
databaseUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
|
||||
checkUnlockAvailability(databaseUri)
|
||||
}
|
||||
} ?: checkUnlockAvailability(null)
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
allowAdvancedUnlockMenu = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun closeBiometricPrompt() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cryptoPromptState = DeviceUnlockPromptMode.CLOSE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun biometricPromptClosed() {
|
||||
cryptoPrompt = null
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cryptoPromptState = DeviceUnlockPromptMode.IDLE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
changeMode(DeviceUnlockMode.BIOMETRIC_UNAVAILABLE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
deviceUnlockManager = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DeviceUnlockPromptMode {
|
||||
IDLE, SHOW, CLOSE
|
||||
}
|
||||
|
||||
data class DeviceUnlockState(
|
||||
val newDeviceUnlockMode: DeviceUnlockMode = DeviceUnlockMode.BIOMETRIC_UNAVAILABLE,
|
||||
val allowAdvancedUnlockMenu: Boolean = false,
|
||||
val credentialRequiredCipher: Cipher? = null,
|
||||
val cipherEncryptDatabase: CipherEncryptDatabase? = null,
|
||||
val cipherDecryptDatabase: CipherDecryptDatabase? = null,
|
||||
val cryptoPromptState: DeviceUnlockPromptMode = DeviceUnlockPromptMode.IDLE,
|
||||
val autoOpenPrompt: Boolean = false,
|
||||
val exception: Exception? = null
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
<com.kunzisoft.keepass.view.DeviceUnlockView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/advanced_unlock_view"
|
||||
android:layout_width="match_parent"
|
||||
@@ -18,28 +18,28 @@
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
--><resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="homepage">الصفحة الرئيسة</string>
|
||||
<string name="accept">قبول</string>
|
||||
<string name="add_group">إضافة مجموعة</string>
|
||||
<string name="encryption">التشفير</string>
|
||||
<string name="encryption_algorithm">خوارزمية التشفير</string>
|
||||
<string name="accept">اقبل</string>
|
||||
<string name="add_group">أضف مجموعة</string>
|
||||
<string name="encryption">التعمية</string>
|
||||
<string name="encryption_algorithm">خوارزمية التعمية</string>
|
||||
<string name="application">التطبيق</string>
|
||||
<string name="brackets">الأقواس</string>
|
||||
<string name="extended_ASCII">ASCII ممتد</string>
|
||||
<string name="allow">سماح</string>
|
||||
<string name="allow">اسمح</string>
|
||||
<string name="clipboard_cleared">مُسِحت الحافظة</string>
|
||||
<string name="clipboard_error_title">خطأ في الحافظة</string>
|
||||
<string name="clipboard_error_clear">تعذَّر مسح الحافظة</string>
|
||||
<string name="database">قاعدة البيانات</string>
|
||||
<string name="decrypting_db">يفك تشفير محتوى قاعدة البيانات…</string>
|
||||
<string name="decrypting_db">يفك تعمية محتوى قاعدة البيانات…</string>
|
||||
<string name="digits">أرقام</string>
|
||||
<string name="entry_cancel">إلغاء</string>
|
||||
<string name="entry_cancel">ألغِ</string>
|
||||
<string name="entry_notes">ملاحظات</string>
|
||||
<string name="entry_confpassword">تأكيد كلمة السر</string>
|
||||
<string name="entry_confpassword">أكّد كلمة السر</string>
|
||||
<string name="entry_created">أُنشئ</string>
|
||||
<string name="entry_modified">معدل</string>
|
||||
<string name="entry_modified">مُعدل</string>
|
||||
<string name="entry_not_found">تعذر العثور على بيانات المُدخلة.</string>
|
||||
<string name="entry_password">كلمة السر</string>
|
||||
<string name="save">حفظ</string>
|
||||
<string name="save">احفظ</string>
|
||||
<string name="entry_title">العنوان</string>
|
||||
<string name="entry_url">رابط</string>
|
||||
<string name="entry_user_name">اسم المستخدم</string>
|
||||
@@ -49,8 +49,8 @@
|
||||
<string name="error_pass_match">كلمتا السر غير متطابقتين.</string>
|
||||
<string name="field_name">اسم الحقل</string>
|
||||
<string name="field_value">قيمة الحقل</string>
|
||||
<string name="generate_password">توليد كلمة سر</string>
|
||||
<string name="hint_conf_pass">تأكيد كلمة السر</string>
|
||||
<string name="generate_password">ولّد كلمة سر</string>
|
||||
<string name="hint_conf_pass">أكّد كلمة السر</string>
|
||||
<string name="hint_group_name">اسم المجموعة</string>
|
||||
<string name="hint_length">الطول</string>
|
||||
<string name="hint_pass">كلمة السر</string>
|
||||
@@ -61,15 +61,15 @@
|
||||
<string name="list_size_summary">حجم النص في قائمة العناصر</string>
|
||||
<string name="loading_database">يحمل قاعدة البيانات…</string>
|
||||
<string name="lowercase">حروف صغيرة</string>
|
||||
<string name="hide_password_summary">إخفاء كلمات السر بشكل افتراضي</string>
|
||||
<string name="hide_password_summary">أخفِ كلمات السر (***) افتراضيًا</string>
|
||||
<string name="about">عن التطبيق</string>
|
||||
<string name="menu_change_key_settings">تغيير المفتاح الرئيسي</string>
|
||||
<string name="settings">الإعدادات</string>
|
||||
<string name="menu_app_settings">إعدادات التطبيق</string>
|
||||
<string name="menu_database_settings">إعدادات قاعدة البيانات</string>
|
||||
<string name="menu_delete">حذف</string>
|
||||
<string name="menu_delete">احذف</string>
|
||||
<string name="menu_donate">التبرع</string>
|
||||
<string name="menu_edit">تعديل</string>
|
||||
<string name="menu_edit">عدّل</string>
|
||||
<string name="menu_lock">اقفل قاعدة البيانات</string>
|
||||
<string name="menu_open">فتح</string>
|
||||
<string name="menu_search">البحث</string>
|
||||
@@ -80,7 +80,7 @@
|
||||
<string name="progress_create">إنشاء قاعدة بيانات جديدة …</string>
|
||||
<string name="protection">الحماية</string>
|
||||
<string name="read_only">محمي من التعديل</string>
|
||||
<string name="content_description_remove_from_list">إزالة</string>
|
||||
<string name="content_description_remove_from_list">أزل</string>
|
||||
<string name="root">الجذر</string>
|
||||
<string name="memory_usage">استخدام الذاكرة</string>
|
||||
<string name="parallelism">التَّوازِي</string>
|
||||
@@ -107,20 +107,19 @@
|
||||
<string name="feedback">أرسل انطباعاتك</string>
|
||||
<string name="about_description">\"KeePassDX\" هو تطبيق أندرويد لمدير كلمات المرور كي باس \"KeePass\"</string>
|
||||
<string name="add_entry">أضف مدخل</string>
|
||||
<string name="edit_entry">تحرير مدخل</string>
|
||||
<string name="edit_entry">عدّل مدخل</string>
|
||||
<string name="key_derivation_function">وظيفة اشتقاق المفتاح</string>
|
||||
<string name="app_timeout">المهلة</string>
|
||||
<string name="app_timeout_summary">مدة الخمول قبل قفل قاعدة البيانات</string>
|
||||
<string name="file_manager_install_description">مدير الملفات الذي يمكنه القيام بالإجراءين ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانشاء, وفتح وحفض قواعد البيانات.</string>
|
||||
<string name="file_manager_install_description">مدير الملفات الذي يمكنه القيام بالإجراءين ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانشاء، وفتح وحفظ قواعد البيانات.</string>
|
||||
<string name="clipboard_error">بعض الأجهزة لا تسمح للتطبيقات باستعمال الحافظة.</string>
|
||||
<string name="clipboard_timeout">مهلة الحافظة</string>
|
||||
<string name="clipboard_timeout_summary">مدة التخزين في الحافظة(إذا كان جهازك يدعمها)</string>
|
||||
<string name="clipboard_timeout_summary">مدة التخزين في الحافظة (إذا كان جهازك يدعمها)</string>
|
||||
<string name="select_to_copy">اختر لنسخ %1$s إلى الحافظة</string>
|
||||
<string name="retrieving_db_key">يجلب مفتاح قاعدة البيانات…</string>
|
||||
<string name="default_checkbox">استخدامها كقاعدة بيانات افتراضية</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d كونزيسوفت <strong>مفتوح المصدر</strong> و <strong>بدون اعلانات</strong>.
|
||||
\n يوزع كما هو، بدون ضمان, تحت ترخيص <strong>GPLv3</strong>.</string>
|
||||
<string name="entry_accessed">نُفذ إليه</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d كونزيسوفت <strong>مفتوح المصدر</strong> و <strong>بدون إعلانات</strong>. \n يوزع كما هو، دون ضمان، تحت ترخيص <strong>GPLv3</strong>.</string>
|
||||
<string name="entry_accessed">وُصِل إليه</string>
|
||||
<string name="entry_expires">تنتهي صلاحيته في</string>
|
||||
<string name="entry_keyfile">ملف المفتاح</string>
|
||||
<string name="error_arc4">تشفير دفق Arcfour غير مدعوم.</string>
|
||||
@@ -129,29 +128,29 @@
|
||||
<string name="error_nokeyfile">اختر ملف مفتاح.</string>
|
||||
<string name="error_out_of_memory">لا ذاكرة لتحميل قاعدة البيانات كاملة.</string>
|
||||
<string name="error_load_database">تعذر تحميل قاعدة البيانات.</string>
|
||||
<string name="error_load_database_KDF_memory">لا يمكن تحميل المفتاح، حاول تقليل \"الذاكرة المستخدمة\" من قبل KDF.</string>
|
||||
<string name="error_load_database_KDF_memory">تعذر تحميل المفتاح. حاول تقليل \"الذاكرة المستخدمة\" من قِبل KDF.</string>
|
||||
<string name="error_pass_gen_type">يجب تحديد نوع واحد على الأقل لتوليد كلمة السر.</string>
|
||||
<string name="error_rounds_too_large">\"جولات التحويل\" كثيرة جداً. الإعداد إلى 2147483648.</string>
|
||||
<string name="error_string_key">يجب أن يكون لكل سلسلة اسم حقل.</string>
|
||||
<string name="error_wrong_length">أدخل عددًا صحيحًا موجبًا في حقل «الطول».</string>
|
||||
<string name="error_autofill_enable_service">تعذر تمكين خدمة الملء التلقائي.</string>
|
||||
<string name="file_not_found_content">تعذر إيجاد الملف. جرِّب فتحه من متصفح ملفات.</string>
|
||||
<string name="file_browser">مدير الملفات.</string>
|
||||
<string name="file_browser">مدير الملفات</string>
|
||||
<string name="invalid_credentials">تعذر قراءة الإعتمادات.</string>
|
||||
<string name="invalid_db_sig">تعذر تمييز نسق قاعدة البيانات.</string>
|
||||
<string name="keyfile_is_empty">ملف المفتاح فارغ.</string>
|
||||
<string name="list_entries_show_username_title">أظهر أسماء المستخدمين</string>
|
||||
<string name="list_entries_show_username_summary">اعرض اسماء المستخدمين في قوائم المدخلات</string>
|
||||
<string name="hint_generated_password">كلمة السر الموَلدة.</string>
|
||||
<string name="hint_keyfile">الملف المفتاحي.</string>
|
||||
<string name="hide_password_title">اخفاء كلمات السر</string>
|
||||
<string name="list_entries_show_username_summary">يعرض اسماء المستخدمين في قوائم المدخلات</string>
|
||||
<string name="hint_generated_password">كلمة السر مولّدة</string>
|
||||
<string name="hint_keyfile">ملف المفتاح</string>
|
||||
<string name="hide_password_title">أخفِ كلمات السر</string>
|
||||
<string name="copy_field">نُسخة من %1$s</string>
|
||||
<string name="menu_copy">نسخ</string>
|
||||
<string name="menu_move">نقل</string>
|
||||
<string name="menu_paste">لصق</string>
|
||||
<string name="menu_cancel">الغاء</string>
|
||||
<string name="menu_hide_password">اخفاء كلمة السر</string>
|
||||
<string name="menu_showpass">اظهار كلمة السر</string>
|
||||
<string name="menu_cancel">ألغِ</string>
|
||||
<string name="menu_hide_password">أخفِ كلمة السر</string>
|
||||
<string name="menu_showpass">أظهر كلمة السر</string>
|
||||
<string name="menu_url">الانتقال الى الرابط</string>
|
||||
<string name="menu_file_selection_read_only">محمي من التعديل</string>
|
||||
<string name="menu_open_file_read_and_write">قابل للتعديل</string>
|
||||
@@ -166,7 +165,7 @@
|
||||
<string name="unavailable">غير متوفر</string>
|
||||
<string name="menu_appearance_settings">المظهر</string>
|
||||
<string name="general">عام</string>
|
||||
<string name="autofill">ملأ تلقائي</string>
|
||||
<string name="autofill">الملء التلقائي</string>
|
||||
<string name="autofill_sign_in_prompt">سجل باستخدام KeePassDX</string>
|
||||
<string name="set_autofill_service_title">تعيين خدمة الملأ التلقائي الافتراضية</string>
|
||||
<string name="password_size_title">حجم كلمة السر المولدة</string>
|
||||
@@ -178,7 +177,7 @@
|
||||
<string name="clipboard_warning">اذا فشل الحذف التلقائي من الحافظة ,احذف تأريخه يدويا.</string>
|
||||
<string name="lock_database_screen_off_title">قفل الشاشة</string>
|
||||
<string name="lock_database_screen_off_summary">اقفل قاعدة البيانات بعد بضع ثوانٍ بمجرد إيقاف تشغيل الشاشة</string>
|
||||
<string name="biometric_delete_all_key_title">حذف مفاتيح التشفير</string>
|
||||
<string name="biometric_delete_all_key_title">احذف مفاتيح التعمية</string>
|
||||
<string name="unavailable_feature_text">لا يمكن بدأ هذه الميزة .</string>
|
||||
<string name="unavailable_feature_version">هذا الجهاز يعمل بأندرويد %1$s لكن يحتاج نسخة %2$s على الأقل.</string>
|
||||
<string name="file_name">اسم الملف</string>
|
||||
@@ -234,10 +233,10 @@
|
||||
<string name="keyboard_notification_entry_content_title">%1$s متوفر على Magikeyboard</string>
|
||||
<string name="keyboard_notification_entry_content_text">%1$s</string>
|
||||
<string name="reset_education_screens_title">إعادة تعيين التلميحات التعليمية</string>
|
||||
<string name="education_search_title">البحث من خلال الإدخالات</string>
|
||||
<string name="education_search_title">ابحث من خلال المدخلات</string>
|
||||
<string name="content_description_open_file">افتح الملف</string>
|
||||
<string name="content_description_add_entry">إضافة مدخلة</string>
|
||||
<string name="content_description_add_group">إضافة مجموعة</string>
|
||||
<string name="content_description_add_entry">أضف مدخل</string>
|
||||
<string name="content_description_add_group">أضف مجموعة</string>
|
||||
<string name="content_description_file_information">معلومات الملف</string>
|
||||
<string name="entry_password_generator">مولد كلمة السر</string>
|
||||
<string name="content_description_background">الخلفية</string>
|
||||
@@ -250,22 +249,22 @@
|
||||
<string name="do_not_kill_app">لا تقتل التطبيق…</string>
|
||||
<string name="content_description_node_children">العقد الفرعية</string>
|
||||
<string name="content_description_add_node">أضف عقدة</string>
|
||||
<string name="content_description_entry_icon">ايقونة المدخل</string>
|
||||
<string name="content_description_entry_icon">أيقونة المدخل</string>
|
||||
<string name="content_description_password_length">طول كلمة السر</string>
|
||||
<string name="entry_add_field">أضف حقل</string>
|
||||
<string name="content_description_remove_field">أزل حقل</string>
|
||||
<string name="error_move_entry_here">يتعذر نقل مدخل إلى هنا.</string>
|
||||
<string name="error_copy_entry_here">يتعذر نسخ مدخال إلى هنا.</string>
|
||||
<string name="list_groups_show_number_entries_title">عرض عدد المدخلات</string>
|
||||
<string name="list_groups_show_number_entries_summary">عرض عدد المدخلات في المجموعة</string>
|
||||
<string name="content_description_update_from_list">تحديث</string>
|
||||
<string name="error_move_entry_here">لا يمكنك نقل مدخل هنا.</string>
|
||||
<string name="error_copy_entry_here">لا يمكنك نسخ مدخل هنا.</string>
|
||||
<string name="list_groups_show_number_entries_title">أظهر عدد المدخلات</string>
|
||||
<string name="list_groups_show_number_entries_summary">يعرض عدد المدخلات في المجموعة</string>
|
||||
<string name="content_description_update_from_list">حدِّث</string>
|
||||
<string name="content_description_keyboard_close_fields">أغلق الحقول</string>
|
||||
<string name="error_create_database_file">لا يمكن انشاء قاعدة بيانات بكلمة السر وملف المفتاح الحاليين.</string>
|
||||
<string name="error_create_database_file">تعذر إنشاء قاعدة بيانات بكلمة السر وملف المفتاح الحاليين.</string>
|
||||
<string name="menu_advanced_unlock_settings">فك قفل الجهاز</string>
|
||||
<string name="entry_attachments">مرفقات</string>
|
||||
<string name="entry_history">السجل</string>
|
||||
<string name="entry_add_attachment">أضف مرفقا</string>
|
||||
<string name="discard">إلغاء</string>
|
||||
<string name="entry_history">التاريخ</string>
|
||||
<string name="entry_add_attachment">أضف مرفقًا</string>
|
||||
<string name="discard">تجاهل</string>
|
||||
<string name="discard_changes">تجاهل التغييرات؟</string>
|
||||
<string name="validate">تأكيد</string>
|
||||
<string name="security">الأمان</string>
|
||||
@@ -273,27 +272,27 @@
|
||||
<string name="error_otp_period">يجب ان تكون المدة بين %1$d و%2$d ثانية.</string>
|
||||
<string name="error_otp_secret_key">المفتاح السري يجب ان يكون بصيغة Base32.</string>
|
||||
<string name="error_save_database">تعذر حفظ قاعدة البيانات.</string>
|
||||
<string name="error_create_database">لا يمكن إنشاء ملف قاعدة البيانات.</string>
|
||||
<string name="error_copy_group_here">لا يمكن نسخ مجموعة هنا.</string>
|
||||
<string name="error_create_database">تعذر إنشاء ملف قاعدة البيانات.</string>
|
||||
<string name="error_copy_group_here">لا يمكنك نسخ مجموعة هنا.</string>
|
||||
<string name="error_label_exists">هذه التسمية موجودة بالفعل.</string>
|
||||
<string name="otp_period">المدة (ثواني)</string>
|
||||
<string name="otp_algorithm">الخوارزمية</string>
|
||||
<string name="otp_digits">أرقام</string>
|
||||
<string name="otp_counter">العداد</string>
|
||||
<string name="entry_setup_otp">عيّن كلمة مرور لمرة واحدة</string>
|
||||
<string name="entry_setup_otp">عيّن كلمة سر لمرة واحدة</string>
|
||||
<string name="entry_UUID">UUID</string>
|
||||
<string name="html_about_contribution">من أجل <strong>حماية خصوصيتا</strong>٫<strong> إصلاح العلل</strong>٫ <strong>إضافة مميزات</strong> <strong>وجعلنا نشطاء دائما</strong>٫ نحن نعتمد على <strong>مساهمتك</strong>.</string>
|
||||
<string name="content_description_keyfile_checkbox">خانة تأشير الملف المفتاحي</string>
|
||||
<string name="html_about_contribution">لكي <strong>نحافظ على حريتنا</strong>، و<strong>نصلح الأخطاء</strong>، و<strong>نضيف ميزات</strong>، و<strong>نبقى دائمًا نشطين</strong>، فإننا نعتمد على <strong>مساهمتكم</strong>.</string>
|
||||
<string name="content_description_keyfile_checkbox">خانة تأشير ملف المفتاح</string>
|
||||
<string name="content_description_password_checkbox">خانة تأشير كلمة السر</string>
|
||||
<string name="content_description_add_item">أضف عنصر</string>
|
||||
<string name="warning_permanently_delete_nodes">حذف العقد المحددة نهائيا؟</string>
|
||||
<string name="filter">مرشح</string>
|
||||
<string name="command_execution">ينفذ الأمر…</string>
|
||||
<string name="hide_broken_locations_title">اِخفي روابط قواعد البيانات المعطلة</string>
|
||||
<string name="hide_broken_locations_title">أخفِ روابط قواعد البيانات المعطوبة</string>
|
||||
<string name="show_recent_files_summary">أظهر موقع قواعد البيانات الأخيرة</string>
|
||||
<string name="show_recent_files_title">أظهر الملفات الأخيرة</string>
|
||||
<string name="remember_keyfile_locations_summary">تعقب موقع الملفات المفتاحية لقاعدة البيانات</string>
|
||||
<string name="remember_keyfile_locations_title">تذكر موقع الملف المفتاحي</string>
|
||||
<string name="remember_keyfile_locations_title">تذكر موقع ملف المفتاح</string>
|
||||
<string name="remember_database_locations_summary">تعقب موقع قاعدة البيانات</string>
|
||||
<string name="remember_database_locations_title">تذكر موقع تخزين قاعدة البيانات</string>
|
||||
<string name="contains_duplicate_uuid_procedure">للمتابعة هل تريد حل المشكلة بتوليد UUID للعناصر المكررة ؟</string>
|
||||
@@ -308,42 +307,42 @@
|
||||
<string name="creating_database">ينشئ قاعدة البيانات…</string>
|
||||
<string name="error_string_type">لا يطابق هذا النص العنصر المطلوب.</string>
|
||||
<string name="error_otp_counter">على العداد أن يكون ما بين %1$d و %2$d.</string>
|
||||
<string name="entry_otp">كلمة مرور لمرة واحدة</string>
|
||||
<string name="otp_type">نوع كلمة المرور لمرة واحدة</string>
|
||||
<string name="entry_otp">كلمة سر لمرة واحدة</string>
|
||||
<string name="otp_type">نوع كلمة السر لمرة واحدة (OTP)</string>
|
||||
<string name="error_disallow_no_credentials">عين اعتماد واحد على الأقل.</string>
|
||||
<string name="contribution">ساهم</string>
|
||||
<string name="contact">الإتصال بنا</string>
|
||||
<string name="contact">التواصل</string>
|
||||
<string name="biometric">البصمة</string>
|
||||
<string name="warning_empty_keyfile_explanation">يجب ألا تغير محتوى ملف المفتاح، في أحسن الحالات يجب أن يحتوي بيانات مولدة عشوائيا.</string>
|
||||
<string name="warning_empty_keyfile">من غير المستحسن اضافة ملف مفتاح فارغ.</string>
|
||||
<string name="warning_sure_remove_data">أزل هذه البيانات عل أي حال؟</string>
|
||||
<string name="warning_sure_add_file">أأضف الملف على أي حال؟</string>
|
||||
<string name="warning_replace_file">رفعُ هذا الملف سيستبدل الموجود مسبقا.</string>
|
||||
<string name="warning_replace_file">رفع هذا الملف سيستبدل الموجود مسبقًا.</string>
|
||||
<string name="warning_database_link_revoked">أبطلَ مدير الملفات الوصول للملف</string>
|
||||
<string name="warning_database_read_only">أعط صلاحية الكتابة من أجل حفظ قاعدة البيانات</string>
|
||||
<string name="warning_password_encoding">تجنب استخدام المحارف غير الموجودة في ترميز قاعدة البيانات (تحوَّل المحارف غير الموجودة لنفس الحرف).</string>
|
||||
<string name="hide_broken_locations_summary">إخف الروابط المعطلة في قائمة قواعد البيانات الحديثة</string>
|
||||
<string name="hide_broken_locations_summary">أخفِ الروابط المعطوبة في قائمة قواعد البيانات الحديثة</string>
|
||||
<string name="auto_focus_search_summary">افتح البحث عند فتح قاعدة البيانات</string>
|
||||
<string name="content_description_credentials_information">معلومات بيانات الاعتماد</string>
|
||||
<string name="max_history_items_title">العدد الأقصى</string>
|
||||
<string name="recycle_bin_group_title">مجموعة سلة المحذوفات</string>
|
||||
<string name="recycle_bin_summary">أُنقل المجموعات والمدخلات لسلة المحذوفات قبل حذفها</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">أزِل المرفقات غير المرتبطة بإدخال في قاعدة البيانات</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">أزل المرفقات غير المرتبطة بمدخل في قاعدة البيانات</string>
|
||||
<string name="database_data_remove_unlinked_attachments_title">أزل البيانات غير المرتبطة</string>
|
||||
<string name="database_data_compression_summary">ضغط البيانات يقلص من حجم قاعدة البيانات</string>
|
||||
<string name="database_data_compression_title">ضغط البيانات</string>
|
||||
<string name="data">البيانات</string>
|
||||
<string name="unavailable_feature_hardware">تعذر العثور على ماسح البصمة.</string>
|
||||
<string name="biometric_delete_all_key_summary">احذف كل مفاتيح التشفير المرتبطة بفتح الجهاز</string>
|
||||
<string name="biometric_delete_all_key_summary">احذف كل مفاتيح التعمية المرتبطة بفتح الجهاز</string>
|
||||
<string name="advanced_unlock_explanation_summary">استخدم إلغاء القفل الجهاز لفتح قاعدة البيانات بسهولة</string>
|
||||
<string name="lock_database_show_button_summary">يعرض زر القَفل في الواجهة</string>
|
||||
<string name="lock_database_show_button_title">اعرض زر القَفل</string>
|
||||
<string name="lock_database_show_button_title">أظهر زر القفل</string>
|
||||
<string name="lock_database_back_root_summary">قفل قاعدة البيانات عند النقر على زر الرجوع في الشاشة الرئيسية</string>
|
||||
<string name="lock_database_back_root_title">اضغط على \"رجوع\" للإقفال</string>
|
||||
<string name="clipboard_explanation_summary">انسخ حقول الإدخال باستخدام الحافظة</string>
|
||||
<string name="clipboard_explanation_summary">انسخ حقول المدخل باستخدام الحافظة</string>
|
||||
<string name="database_opened">قاعدة البيانات مفتوحة</string>
|
||||
<string name="autofill_preference_title">إعدادات الملء التلقائي</string>
|
||||
<string name="education_entry_edit_title">حرر المدخلة</string>
|
||||
<string name="education_entry_edit_title">عدّل المدخل</string>
|
||||
<string name="education_advanced_unlock_summary">لفتح قاعدة البيانات بسرعة اربط كلمة المرور بالبصمة.</string>
|
||||
<string name="education_search_summary">لإيجاد كلمة المرور، أدخل العنوان أو اسم المستخدم أو محتوى أحد الحقول.</string>
|
||||
<string name="education_new_node_summary">المدخلات لإدارة معرفاتك الرقمية.
|
||||
@@ -357,15 +356,15 @@
|
||||
<string name="autofill_web_domain_blocklist_title">قائمة النطاقات المحظورة</string>
|
||||
<string name="autofill_application_id_blocklist_summary">منع الملء التلقائي للتطبيقات الموجودة في القائمة</string>
|
||||
<string name="autofill_application_id_blocklist_title">قائمة التطبيقات المحظورة</string>
|
||||
<string name="content_description_repeat_toggle_password_visibility">بدِّل ظهور كلمة السر</string>
|
||||
<string name="hide_expired_entries_summary">لن تعرض المدخلات منتهية الصلاحية</string>
|
||||
<string name="content_description_repeat_toggle_password_visibility">أعد تبديل ظهور كلمة السر</string>
|
||||
<string name="hide_expired_entries_summary">لا يتم عرض المدخلات منتهية الصلاحية</string>
|
||||
<string name="education_read_only_summary">تغيير وضع الافتتاح للجلسة.
|
||||
\n
|
||||
\nيمنع \"محمي ضد الكتابة\" التغييرات غير المقصودة في قاعدة البيانات.
|
||||
\n\"قابل للتعديل\" يتيح لك إضافة أو حذف أو تعديل جميع العناصر كما تريد.</string>
|
||||
<string name="education_read_only_title">احمي قاعدة البيانات من التعديل</string>
|
||||
<string name="education_unlock_title">افتح قاعدة البيانات</string>
|
||||
<string name="education_add_attachment_summary">أضف مرفقا للمدخلة لحفظ بيانات اضافية.</string>
|
||||
<string name="education_add_attachment_summary">ارفع مرفقًا إلى مدخلك لحفظ البيانات الخارجية الهامة.</string>
|
||||
<string name="education_add_attachment_title">أضف مرفقا</string>
|
||||
<string name="autofill_block">احظر الملء التلقائي</string>
|
||||
<string name="keyboard_previous_database_credentials_title">شاشة بيانات اعتماد قاعدة البيانات</string>
|
||||
@@ -402,7 +401,7 @@
|
||||
<string name="education_generate_password_title">أنشئ كلمة سر قوية</string>
|
||||
<string name="save_mode">وضع الحفظ</string>
|
||||
<string name="search_mode">وضع البحث</string>
|
||||
<string name="version">النسخة</string>
|
||||
<string name="version">النُسخة</string>
|
||||
<string name="template_group_name">النماذج</string>
|
||||
<string name="holder">الحامل</string>
|
||||
<string name="number">الرقم</string>
|
||||
@@ -410,7 +409,7 @@
|
||||
<string name="personal_identification_number">PIN</string>
|
||||
<string name="id_card">بطاقة الهوية</string>
|
||||
<string name="type">النوع</string>
|
||||
<string name="cryptocurrency">محفظة عملات مشفرة</string>
|
||||
<string name="cryptocurrency">محفظة عملات التعموية</string>
|
||||
<string name="public_key">المفتاح العمومي</string>
|
||||
<string name="private_key">المفتاح الخاص</string>
|
||||
<string name="account">الحساب</string>
|
||||
@@ -418,13 +417,13 @@
|
||||
<string name="bank_name">اسم المصرف</string>
|
||||
<string name="secure_note">ملاحظة آمنة</string>
|
||||
<string name="error_word_reserved">هذه الكلمة محجوزة ولا يمكن استخدامها.</string>
|
||||
<string name="error_field_name_already_exists">اسم الحقل موجود سلفًا.</string>
|
||||
<string name="error_file_to_big">الملف الذي ترفعه كبير.</string>
|
||||
<string name="error_field_name_already_exists">اسم الحقل موجود بالفعل.</string>
|
||||
<string name="error_file_to_big">الملف الذي تحاول رفعه كبير جدًا.</string>
|
||||
<string name="error_upload_file">حدث خطأ أثناء رفع الملف.</string>
|
||||
<string name="error_duplicate_file">بيانات الملف موجودة سلفًا.</string>
|
||||
<string name="error_duplicate_file">بيانات الملف موجودة بالفعل.</string>
|
||||
<string name="error_remove_file">حدث خطأ أثناء إزالة بيانات الملف.</string>
|
||||
<string name="error_start_database_action">حدث خطأ أثناء تنفيذ إجراء على قاعدة البيانات.</string>
|
||||
<string name="content_description_otp_information">معلومات كلمة المرور لمرة واحدة</string>
|
||||
<string name="content_description_otp_information">معلومات كلمة السر لمرة واحدة</string>
|
||||
<string name="membership">العضوية</string>
|
||||
<string name="name">الاسم</string>
|
||||
<string name="email">البريد الإلكتروني</string>
|
||||
@@ -452,8 +451,8 @@
|
||||
<string name="properties">الخصائص</string>
|
||||
<string name="token">الرمز</string>
|
||||
<string name="seed">البذرة</string>
|
||||
<string name="error_database_uri_null">يتعذر استرداد مسار قاعدة البيانات.</string>
|
||||
<string name="error_rebuild_list">يتعذر إعادة بناء القائمة بشكل صحيح.</string>
|
||||
<string name="error_database_uri_null">لا يمكن استرداد URI قاعدة البيانات.</string>
|
||||
<string name="error_rebuild_list">تعذر إعادة بناء القائمة بشكل صحيح.</string>
|
||||
<string name="menu_keystore_remove_key">احذف رمز فك القفل الجهاز</string>
|
||||
<string name="menu_form_filling_settings">ملء النموذج</string>
|
||||
<string name="menu_reload_database">أعد تحميل البيانات</string>
|
||||
@@ -474,7 +473,7 @@
|
||||
<string name="keyboard_previous_fill_in_title">العودة إلى الوراء</string>
|
||||
<string name="keyboard_previous_lock_title">اقفل قاعدة البيانات</string>
|
||||
<string name="education_advanced_unlock_title">فتح قاعدة بيانات الجهاز</string>
|
||||
<string name="hint_icon_name">اسم الأيقونة.</string>
|
||||
<string name="hint_icon_name">اسم الأيقونة</string>
|
||||
<string name="autofill_manual_selection_title">اختيار يدوي</string>
|
||||
<string name="description_app_properties">خصائص KeePassDX لإدارة إعدادات التطبيقات</string>
|
||||
<string name="warning_empty_recycle_bin">هل تريد حذف جميع العقد نهائيًا من سلة المهملات؟</string>
|
||||
@@ -482,7 +481,7 @@
|
||||
<string name="autofill_ask_to_save_data_title">اسأل لحفظ البيانات</string>
|
||||
<string name="content_description_database_color">لون قاعدة البيانات</string>
|
||||
<string name="menu_merge_from">ادمج من…</string>
|
||||
<string name="show_uuid_summary">يعرض \"المعرف العام\" المرتبط بمُدخل او بمجموعة</string>
|
||||
<string name="show_uuid_summary">يعرض UUID المرتبط بمدخل أو بمجموعة</string>
|
||||
<string name="expired">انتهت</string>
|
||||
<string name="tags">الوسوم</string>
|
||||
<string name="menu_merge_database">ادمج البيانات</string>
|
||||
@@ -491,10 +490,10 @@
|
||||
<string name="warning_file_too_big">يفترض بقاعدة البيانات أن تحوي ملفات صغيرة الحجم ( كمفاتيح PGP).
|
||||
\n
|
||||
\nبرفع هذا الملف قد يزداد حجم قاعدة البيانات ويضعف أداءها.</string>
|
||||
<string name="error_move_group_here">يتعذر نقل المجموعة إلى هنا.</string>
|
||||
<string name="error_move_group_here">لا يمكنك نقل مجموعة هنا.</string>
|
||||
<string name="menu_save_copy_to">احفظ نسخة إلى…</string>
|
||||
<string name="searchable">يمكن البحث عنه</string>
|
||||
<string name="custom_data">بيانات مخصصة</string>
|
||||
<string name="custom_data">بيانات مخصّصة</string>
|
||||
<string name="case_sensitive">حساسة لحالة الأحرف</string>
|
||||
<string name="regex">تعابير نمطية</string>
|
||||
<string name="enable_keep_screen_on_title">أبقِ الشاشة شغّالة</string>
|
||||
@@ -512,18 +511,18 @@
|
||||
<string name="templates_group_uuid_title">مجموعة القوالب</string>
|
||||
<string name="advanced_unlock_timeout">انتهت مهلة فتح الجهاز</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">مهلة استخدام فتح الجهاز قبل حذف محتواها</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">أتريد حذف كل مفاتيح التشفير المرتبطة بفتح الجهاز؟</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">أتريد حذف كل مفاتيح التعمية المرتبطة بفتح الجهاز؟</string>
|
||||
<string name="templates">القوالب</string>
|
||||
<string name="templates_group_enable_title">استخدام القوالب</string>
|
||||
<string name="notification">الإشعارات</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">لا تقم بتخزين أي محتوى مشفر لاستخدام إلغاء قفل الجهاز</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">انتهاء صلاحية فتح الحهاز</string>
|
||||
<string name="hide_expired_entries_title">إخفاء الإدخالات منتهية الصلاحية</string>
|
||||
<string name="content_description_hardware_key_checkbox">خانة إختيار مفتاح الجهاز</string>
|
||||
<string name="hide_expired_entries_title">أخفِ المدخلات منتهية الصلاحية</string>
|
||||
<string name="content_description_hardware_key_checkbox">خانة إختيار مفتاح العتاد</string>
|
||||
<string name="content_description_passphrase_word_count">عدد عبارات المرور</string>
|
||||
<string name="content_description_entry_background_color">لون خلفية المدخل</string>
|
||||
<string name="passphrase">عبارة المرور</string>
|
||||
<string name="colorize_password_title">تلوين كلمات المرور</string>
|
||||
<string name="passphrase">عبارة السر</string>
|
||||
<string name="colorize_password_title">لوّن كلمات السر</string>
|
||||
<string name="permission">الإذن</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">تعذر تهيئة موجه إلغاء قفل الجهاز.</string>
|
||||
<string name="biometric_security_update_required">مطلوب تحديث أمان المقاييس الحيوية.</string>
|
||||
@@ -536,12 +535,12 @@
|
||||
\nاستخدم طريقة ملء النموذج التي تفضلها.</string>
|
||||
<string name="html_text_dev_feature_work_hard">نحن نعمل بجد لإصدار هذه الميزة بسرعة.</string>
|
||||
<string name="autofill_inline_suggestions_summary">حاول عرض اقتراحات الملء التلقائي مباشرة من لوحة مفاتيح متوافقة</string>
|
||||
<string name="delete_entered_password_summary">يحذف كلمة المرور التي تم إدخالها بعد محاولة الاتصال بقاعدة البيانات</string>
|
||||
<string name="delete_entered_password_summary">يحذف كلمة السر التي أُدخلت بعد محاولة الاتصال بقاعدة البيانات</string>
|
||||
<string name="education_lock_summary">اقفل قاعدةبياناتك بسرعة، يمكنك إعداد التطبيق لقفلها بعد فترة، وعند إيقاف تشغيل الشاشة.</string>
|
||||
<string name="education_sort_title">فرز العنصر</string>
|
||||
<string name="contribute">ساهِم</string>
|
||||
<string name="upload_attachment">رفع %1$s</string>
|
||||
<string name="download_canceled">ألغيت!</string>
|
||||
<string name="upload_attachment">ارفع %1$s</string>
|
||||
<string name="download_canceled">أُلغِيَ!</string>
|
||||
<string name="unit_kibibyte">كيلو بايت</string>
|
||||
<string name="unit_mebibyte">ميغا بايت</string>
|
||||
<string name="unit_gibibyte">جيجابت</string>
|
||||
@@ -557,7 +556,7 @@
|
||||
<string name="title_case">حالة العنوان</string>
|
||||
<string name="character_count">عدد الأحرف: %1$d</string>
|
||||
<string name="style_choose_summary">السمة المستخدمة في التطبيق</string>
|
||||
<string name="show_entry_colors_summary">يعرض ألوان المقدمة والخلفية لإدخال</string>
|
||||
<string name="show_entry_colors_summary">يعرض ألوان المقدمة والخلفية للمدخل</string>
|
||||
<string name="icon_pack_choose_summary">حزمة الأيقونات المستخدمة في التطبيق</string>
|
||||
<string name="show_entry_colors_title">ألوان الدخول</string>
|
||||
<string name="device_credential_unlock_enable_title">فتح بيانات اعتماد الجهاز</string>
|
||||
@@ -574,15 +573,15 @@
|
||||
<string name="keyboard_previous_fill_in_summary">العودة تلقائيًا إلى لوحة المفاتيح السابقة بعد تنفيذ \"إجراء المفتاح التلقائي\"</string>
|
||||
<string name="download_attachment">تثبيت %1$s</string>
|
||||
<string name="html_about_privacy"><strong> لا يتم استرداد أي بيانات مستخدم</strong>، هذا التطبيق لا يتصل بأي خادم، ويعمل محليًا فقط ويحترم خصوصية المستخدمين تمامًا.</string>
|
||||
<string name="error_cancel_by_user">ألغى المستخدم.</string>
|
||||
<string name="show_otp_token_title">إظهار رمز \"الاقتران لمرة واحدة\" OTP</string>
|
||||
<string name="show_otp_token_summary">إظهار رموز\"الاقتران لمرة واحدة\" في قائمة المدخلات</string>
|
||||
<string name="error_cancel_by_user">أُلغِيَ بواسطة المستخدم.</string>
|
||||
<string name="show_otp_token_title">أظهر رمز OTP</string>
|
||||
<string name="show_otp_token_summary">يعرض رموز OTP في قائمة المدخلات</string>
|
||||
<string name="warning_database_already_opened">قاعدة البيانات مفتوحة بالفعل، أغلقها أولاً لفتح قاعدة البيانات الجديدة</string>
|
||||
<string name="warning_database_info_reloaded">ستؤدي إعادة تحميل قاعدة البيانات إلى حذف البيانات المعدلة محليًا.</string>
|
||||
<string name="templates_group_enable_summary">استخدم القوالب الديناميكية لملء حقول الإدخال</string>
|
||||
<string name="templates_group_enable_summary">استخدم القوالب الديناميكية لملء حقول المدخل</string>
|
||||
<string name="keyboard_auto_go_action_summary">إجراء مفتاح \"Go\" بعد الضغط على مفتاح \"Field\"</string>
|
||||
<string name="allow_no_password_summary">يسمح بالنقر فوق الزر \"فتح\" إذا لم يتم تحديد بيانات اعتماد</string>
|
||||
<string name="education_generate_password_summary">أنشئ كلمة مرور قوية لربطها بإدخالك، وحددها بسهولة وفقًا لمعايير النموذج ولا تنس كلمة المرور الآمنة.</string>
|
||||
<string name="education_generate_password_summary">أنشئ كلمة سر قوية لربطها بإدخالك، وحددها بسهولة وفقًا لمعايير النموذج ولا تنسَ كلمة السر الآمنة.</string>
|
||||
<string name="education_setup_OTP_title">قم بإعداد OTP</string>
|
||||
<string name="style_brightness_title">سطوع السمة</string>
|
||||
<string name="word_separator">الفاصل</string>
|
||||
@@ -597,26 +596,26 @@
|
||||
<string name="kdf_explanation">لإنشاء مفتاح خوارزمية التشفير، يتحول المفتاح الرئيسي باستخدام وظيفة اشتقاق مفتاح مملح عشوائيًا.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">بشراء الإصدار <strong> pro </strong>،</string>
|
||||
<string name="auto_type">كتابة تلقائيًا</string>
|
||||
<string name="hardware_key">مفتاح الجهاز</string>
|
||||
<string name="hardware_key">مفتاح العتاد</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">رابط لفتح الجهاز</string>
|
||||
<string name="backspace">فراغ للخلف</string>
|
||||
<string name="enter">دخول</string>
|
||||
<string name="education_sort_summary">اختر كيفية فرز الإدخالات والمجموعات.</string>
|
||||
<string name="education_sort_summary">اختر كيفية فرز المدخلات والمجموعات.</string>
|
||||
<string name="html_text_feature_generosity">هذا <strong> النمط المرئي</strong> متاح بفضل كرمك.</string>
|
||||
<string name="info">المعلومات</string>
|
||||
<string name="waiting_challenge_response">في انتظار استجابة التحدي…</string>
|
||||
<string name="bank_identifier_code">SWIFT / BIC</string>
|
||||
<string name="international_bank_account_number">IBAN</string>
|
||||
<string name="error_no_hardware_key">حدد مفتاح الجهاز.</string>
|
||||
<string name="colorize_password_summary">تلوين أحرف كلمة المرور حسب النوع</string>
|
||||
<string name="enable_keep_screen_on_summary">استمر في تشغيل الشاشة عند مشاهدة إدخال أو تعديله</string>
|
||||
<string name="error_no_hardware_key">حدّد مفتاح العتاد.</string>
|
||||
<string name="colorize_password_summary">لوّن أحرف كلمة السر حسب النوع</string>
|
||||
<string name="enable_keep_screen_on_summary">استمر في تشغيل الشاشة عند مشاهدة مدخل أو تعديله</string>
|
||||
<string name="enable_screenshot_mode_title">وضع لقطة الشاشة</string>
|
||||
<string name="navigation_drawer_open">درج التنقل مفتوح</string>
|
||||
<string name="waiting_challenge_request">في انتظار طلب التحدي…</string>
|
||||
<string name="navigation_drawer_close">درج التنقل مقفول</string>
|
||||
<string name="error_XML_malformed">XML تالف.</string>
|
||||
<string name="error_otp_type">لم يتم التعرف على نوع OTP الحالي من خلال هذا النموذج، وقد لا يؤدي التحقق من صحته إلى إنشاء الرمز المميز بشكل صحيح.</string>
|
||||
<string name="error_challenge_already_requested">التحدي مطلوب بالفعل.</string>
|
||||
<string name="error_challenge_already_requested">التحدي طُلَب بالفعل.</string>
|
||||
<string name="error_response_already_provided">تقدم الرد بالفعل.</string>
|
||||
<string name="error_no_response_from_challenge">غير قادر على الحصول على رد من التحدي.</string>
|
||||
<string name="error_driver_required">مطلوب تعريف لـ%1$s.</string>
|
||||
@@ -625,15 +624,15 @@
|
||||
<string name="menu_advanced_unlock_settings_summary">القياس الحيوي، بيانات اعتماد الجهاز</string>
|
||||
<string name="menu_database_settings_summary">البيانات الوصفية، سلة المحذوفات، القوالب، التاريخ</string>
|
||||
<string name="menu_security_settings_summary">التشفير، وظيفة اشتقاق المفتاح</string>
|
||||
<string name="error_hardware_key_unsupported">مفتاح الجهاز غير مدعوم.</string>
|
||||
<string name="error_hardware_key_unsupported">مفتاح العتاد غير مدعوم.</string>
|
||||
<string name="master_key_settings_summary">التغيير والتجديد</string>
|
||||
<string name="error_empty_key">لا يمكن أن يكون المفتاح فارغًا.</string>
|
||||
<string name="corrupted_file">ملف تالف.</string>
|
||||
<string name="warning_keyfile_integrity">لا يتم ضمان تجزئة الملف لأن Android يمكنه تغيير بياناته بسرعة. قم بتغيير امتداد الملف إلى bin. من أجل التكامل الصحيح.</string>
|
||||
<string name="invalid_db_same_uuid">%1$s بنفس UUID %2$s موجود بالفعل.</string>
|
||||
<string name="remember_hardware_key_title">تذكر مفاتيح الأجهزة</string>
|
||||
<string name="remember_hardware_key_title">تذكر مفاتيح العتاد</string>
|
||||
<string name="warning_exact_alarm">لم تسمح للتطبيق باستخدام منبه دقيق. نتيجة لذلك، لن يتم تنفيذ الميزات التي تتطلب مؤقتًا في وقت محدد.</string>
|
||||
<string name="remember_hardware_key_summary">يتتبع مفاتيح الأجهزة المستخدمة</string>
|
||||
<string name="remember_hardware_key_summary">يتتبع مفاتيح العتاد المستخدمة</string>
|
||||
<string name="warning_database_notification_permission">يسمح لك إذن الإشعار بعرض حالة قاعدة البيانات وقفلها باستخدام زر يسهل الوصول إليه.
|
||||
\n
|
||||
\nإذا لم تنشط هذا الإذن، فلن تكون قاعدة البيانات المفتوحة في الخلفية مرئية إذا كان هناك تطبيق آخر في المقدمة.</string>
|
||||
@@ -646,10 +645,9 @@
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">استخراج بيانات اعتماد قاعدة البيانات مع بيانات فتح الجهاز</string>
|
||||
<string name="ask">إسأل</string>
|
||||
<string name="configure_biometric">لم تسجل بيانات اعتماد المقاييس الحيوية أو الجهاز.</string>
|
||||
<string name="show_uuid_title">إظهار \"المعرف العام المميز\" UUID</string>
|
||||
<string name="show_uuid_title">أظهر \"المعرّف العام المميز\" UUID</string>
|
||||
<string name="unlock_and_link_biometric">رابط فتح الجهاز</string>
|
||||
<string name="advanced_unlock_invalid_key">لا يمكن قراءة مفتاح فتح الجهاز. يرجى حذفه وتكرار إجراء التعرف على الفتح.</string>
|
||||
<string name="advanced_unlock_scanning_error">خطأ في فتح الجهاز: %1$s</string>
|
||||
<string name="menu_appearance_settings_summary">المظاهر والألوان والسمات</string>
|
||||
<string name="autofill_explanation_summary">تمكين الملء التلقائي لملء النماذج بسرعة في التطبيقات الأخرى</string>
|
||||
<string name="device_credential_unlock_enable_summary">يتيح لك استخدام بيانات اعتماد جهازك لفتح قاعدة البيانات</string>
|
||||
@@ -662,14 +660,12 @@
|
||||
\nاعتمادًا على تطبيق API الأصلي لنظام التشغيل، قد لا يعمل بكامل طاقته.
|
||||
\n
|
||||
\nتحقق من توافق وأمن KeyStore مع الشركة المصنعة لجهازك ومنشئ ROM الذي تستخدمه.</string>
|
||||
<string name="keyboard_selection_entry_summary">عند عرض إدخال في KeePassDX، عبئ Magikeyboard بهذا الإدخال</string>
|
||||
<string name="keyboard_selection_entry_summary">عند عرض مدخل في KeePassDX، عبئ Magikeyboard بهذا المدخل</string>
|
||||
<string name="enable_screenshot_mode_summary">اسمح لتطبيقات الطرف الثالث بتسجيل أو التقاط لقطات شاشة للتطبيق</string>
|
||||
<string name="keyboard_save_search_info_summary">حاول حفظ المعلومات المشتركة عند إجراء اختيار إدخال يدوي لاستخدامات مستقبلية أسهل</string>
|
||||
<string name="education_entry_edit_summary">تحرير الإدخال الخاص بك مع الحقول المخصصة. يمكن الرجوع إلى بيانات التجمع بين حقول الإدخال المختلفة.</string>
|
||||
<string name="education_validate_entry_title">تحقق من صحة الإدخال</string>
|
||||
<string name="education_validate_entry_summary">تذكر التحقق من صحة الإدخال الخاص بك وحفظ قاعدة البيانات الخاصة بك.
|
||||
\n
|
||||
\nإذا تم تنشيط القفل التلقائي ونسيت أنك تجري تعديلاً، فإنك تخاطر بفقدان بياناتك.</string>
|
||||
<string name="keyboard_save_search_info_summary">حاول حفظ المعلومات المشتركة عند إجراء اختيار مدخل يدوي لاستخدامات مستقبلية أسهل</string>
|
||||
<string name="education_entry_edit_summary">عدّل إدخالك مع الحقول المخصّصة. يمكن الرجوع إلى بيانات التجمع بين حقول مدخل المختلفة.</string>
|
||||
<string name="education_validate_entry_title">تحقق من صحة المدخل</string>
|
||||
<string name="education_validate_entry_summary">تذكر التحقق من صحة إدخالك وحفظ قاعدة بياناتك. \n \nإذا القفل التلقائي مُنشّط ونسيت أنك تجري تعديلاً، فإنك تخاطر بفقدان بياناتك.</string>
|
||||
<string name="education_entry_new_field_summary">قم بتسجيل حقل إضافي، أضف قيمة وقم بحمايته بشكل اختياري.</string>
|
||||
<string name="education_unlock_summary">أدخل كلمة المرور و/أو ملف المفتاح لفتح قاعدة بياناتك.
|
||||
\n
|
||||
@@ -697,10 +693,10 @@
|
||||
<string name="style_name_follow_system">اتبع النظام</string>
|
||||
<string name="style_name_light">فاتح</string>
|
||||
<string name="hide_templates_summary">لا يتم عرض القوالب</string>
|
||||
<string name="generate_keyfile">ولّد ملف المفتاح</string>
|
||||
<string name="generate_keyfile">ولّد ملف مفتاح</string>
|
||||
<string name="nodes">العُقد</string>
|
||||
<string name="recursive_number_entries_title">عدد متكرر من الإدخالات</string>
|
||||
<string name="recursive_number_entries_summary">يحسب بشكل متكرر عدد الإدخالات في المجموعة</string>
|
||||
<string name="recursive_number_entries_title">عدد متكرر من المدخلات</string>
|
||||
<string name="recursive_number_entries_summary">يحسب بشكل متكرر عدد المدخلات في المجموعة</string>
|
||||
<string name="warning_large_keyfile">لا يُنصح بإضافة ملف مفتاحي كبير، فقد يؤدي هذا إلى منع فتح قاعدة البيانات.</string>
|
||||
<string name="hide_templates_title">أخفِ القوالب</string>
|
||||
</resources>
|
||||
|
||||
@@ -199,7 +199,6 @@
|
||||
<string name="keystore_not_accessible">Açar ehtiyyatı düzgün formada başladılmadı.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Cihaz kilidini açma linki</string>
|
||||
<string name="database_history">Tarixçə</string>
|
||||
<string name="advanced_unlock_scanning_error">Cihaz kilidini açma xətası: %1$s</string>
|
||||
<string name="warning_database_info_reloaded">Məlumat bazasını yenidən yükləmək lokal olaraq modifikasiya olunmuş faylları siləcəkdir.</string>
|
||||
<string name="warning_database_revoked">Fayla giriş fayl meneceri tərəfindən ləğv edildi, məlumat bazasını bağlayın və onu olduğu yerdən yenidən açın.</string>
|
||||
<string name="warning_exact_alarm">Siz tətəbiqin zəngli saatdan istifadə etməsinə icazə verməmisiniz. Nəticədə, taymer tələb edən funksiyalar dəqiq bir zamanda işləməyəckdir.</string>
|
||||
|
||||
@@ -536,7 +536,6 @@
|
||||
<string name="recycle_bin_title">Използване на кошчето</string>
|
||||
<string name="recycle_bin_summary">Премества групите и записите в групата „Кошче“ вместо да ги премахва директно</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Отключване с устройството</string>
|
||||
<string name="advanced_unlock_scanning_error">Грешка при отключване на устройството: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Не може да бъде разпознато кога устройството е отключено</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Заявката за отключване не може да бъде подготвена.</string>
|
||||
<string name="password_size_summary">Подразбирана дължина на създаваните пароли</string>
|
||||
@@ -685,4 +684,4 @@
|
||||
<string name="warning_large_keyfile">Не се препоръчва използването на голям файл с ключ, защото може да попречи на отварянето на хранилището.</string>
|
||||
<string name="hide_templates_summary">Шаблоните не се показват</string>
|
||||
<string name="hide_templates_title">Скриване на шаблоните</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -366,4 +366,4 @@
|
||||
<string name="parallelism_explanation">কী ডেরিভেশন ফাংশন দ্বারা ব্যবহৃত সমান্তরালতার ডিগ্রি (যেমন থ্রেডের সংখ্যা)।</string>
|
||||
<string name="saving_database">ডাটাবেস সংরক্ষণ করা হচ্ছে…</string>
|
||||
<string name="consider_chars_filter">Aintzat hartu karaktereak</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -613,7 +613,6 @@
|
||||
<string name="configure">Configura</string>
|
||||
<string name="biometric_security_update_required">Cal actualitzar la seguretat biomètrica.</string>
|
||||
<string name="unlock_and_link_biometric">Enllaç de desbloqueig del dispositiu</string>
|
||||
<string name="advanced_unlock_scanning_error">Error en desbloquejar el dispositiu: %1$s</string>
|
||||
<string name="unavailable">No disponible</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">No s\'ha pogut inicialitzar l\'indicador de desbloqueig del dispositiu.</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Escriviu la contrasenya i, a continuació, feu clic en aquest botó.</string>
|
||||
@@ -696,4 +695,4 @@
|
||||
<string name="style_name_sun">Sol</string>
|
||||
<string name="style_name_light">Clar</string>
|
||||
<string name="style_name_dark">Obscur</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
<string name="html_text_ad_free">Na rozdíl od mnoha aplikací pro správu hesel je tato <strong>bez reklam</strong>, je <strong>svobodný software pod copyleft licencí</strong> a nesbírá žádné osobní údaje na svých serverech bez ohledu na to, jakou verzi používáte.</string>
|
||||
<string name="html_text_buy_pro">Zakoupením varianty \"pro\" získáte přístup k tomuto <strong>vizuálnímu stylu</strong> a hlavně pomůžete <strong>uskutečnění komunitních projektů.</strong></string>
|
||||
<string name="html_text_feature_generosity">Tento <strong>vizuální styl</strong> je k dispozici díky vaší štědrosti.</string>
|
||||
<string name="html_text_donation"><strong>Přispěním</strong> projektu <i>(peněžně, kódem, překlady)</i> mu pomůžete žít a prosperovat a dostanete přístup k postupu odemčení <strong>motivů</strong>.</string>
|
||||
<string name="html_text_donation"><strong>Přispěním</strong> do projektu <i>(peněžně, kódem, překlady)</i> mu pomůžete žít a prosperovat a dostanete přístup k postupu odemčení <strong>motivů</strong>.</string>
|
||||
<string name="html_text_dev_feature">Tato funkce je <strong>ve vývoji</strong> a potřebuje Váš <strong>příspěvek</strong>, aby byla brzy k dispozici.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Zakoupením <strong>pro</strong> varianty,</string>
|
||||
<string name="html_text_dev_feature_contibute"><strong>Podpořením vývoje</strong>,</string>
|
||||
@@ -484,7 +484,7 @@
|
||||
<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 uzly z koše\?</string>
|
||||
<string name="registration_mode">Registrace</string>
|
||||
<string name="save_mode">Uložit</string>
|
||||
<string name="save_mode">Režim ukládání</string>
|
||||
<string name="search_mode">Vyhledávání</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í dovoleno.</string>
|
||||
@@ -499,7 +499,6 @@
|
||||
<string name="device_credential">Heslo zařízení</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Zadejte heslo a pak klepněte na toto tlačítko.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Nepodařilo se inicializovat nabídku pro odemykání zařízení.</string>
|
||||
<string name="advanced_unlock_scanning_error">Chyba při odemykání zařízení: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Otisk pro odemykání zařízení nebyl rozpoznán</string>
|
||||
<string name="advanced_unlock_invalid_key">Nepodařilo se načíst klíč odemykání zařízení. Odstraňte ho a opakujte proces rozpoznání odemknutí.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Načíst údaj z databáze pomocí dat odemykání zařízení</string>
|
||||
@@ -709,4 +708,4 @@
|
||||
<string name="warning_large_keyfile">Nedoporučuje se přidávat velký klíčový soubor, mohlo by to zabránit otevření databáze.</string>
|
||||
<string name="hide_templates_title">Skrýt šablony</string>
|
||||
<string name="hide_templates_summary">Šablony nejsou zobrazeny</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -507,7 +507,6 @@
|
||||
<string name="content">Indhold</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Indtast adgangskoden, og klik derefter på denne knap.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Kunne ikke initialisere oplåsningsprompt.</string>
|
||||
<string name="advanced_unlock_scanning_error">Fejl ved oplåsning: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Kunne ikke genkende aftryk til oplåsning</string>
|
||||
<string name="advanced_unlock_invalid_key">Oplåsningsnøgle kan ikke læses. Slet den og gentag proceduren for genkendelse af oplåsning.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Enhedsoplåsningsgenkendelse</string>
|
||||
@@ -701,4 +700,4 @@
|
||||
<string name="style_name_reply">Besvar</string>
|
||||
<string name="style_name_kunzite">Kunzite</string>
|
||||
<string name="style_name_follow_system">Følg systemets tilstand</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -529,7 +529,6 @@
|
||||
<string name="device_credential">Geräteanmeldedaten</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Passwort eingeben und dann diese Taste drücken.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Geräteentsperrungsabfrage konnte nicht gestartet werden.</string>
|
||||
<string name="advanced_unlock_scanning_error">Fehler bei Geräteentsperrung: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Fingerabdruck für Geräteentsperrung wurde nicht erkannt</string>
|
||||
<string name="advanced_unlock_invalid_key">Der Geräteentsperrschlüssel ist nicht lesbar. Bitte diesen löschen und den Vorgang zur Entsperr-Erkennung wiederholen.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Datenbankanmeldedaten aus Geräteentsperrdaten gewinnen</string>
|
||||
@@ -711,4 +710,4 @@
|
||||
<string name="warning_large_keyfile">Es wird nicht empfohlen, eine große Schlüsseldatei hinzuzufügen, da dies das Öffnen der Datenbank verhindern kann.</string>
|
||||
<string name="recursive_number_entries_title">Rekursive Anzahl der Einträge</string>
|
||||
<string name="generate_keyfile">Schlüsseldatei generieren</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -503,7 +503,6 @@
|
||||
<string name="credential_before_click_advanced_unlock_button">Πληκτρολογήστε τον κωδικό πρόσβασης, και στη συνέχεια κάντε κλικ αυτό το κουμπί.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Δεν είναι δυνατή η προετοιμασία της προτροπής ξεκλειδώματος συσκευής.</string>
|
||||
<string name="advanced_unlock_not_recognized">Δεν ήταν δυνατή η αναγνώριση αποτυπώματος ξεκλειδώματος συσκευής</string>
|
||||
<string name="advanced_unlock_scanning_error">Σφάλμα ξεκλειδώματος συσκευής: %1$s</string>
|
||||
<string name="advanced_unlock_invalid_key">Δεν είναι δυνατή η ανάγνωση του κλειδιού ξεκλειδώματος της συσκευής. Διαγράψτε το και επαναλάβετε τη διαδικασία αναγνώρισης ξεκλειδώματος.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Εξαγωγή διαπιστευτηρίων βάσης δεδομένων με δεδομένα ξεκλειδώματος συσκευής</string>
|
||||
<string name="education_advanced_unlock_summary">Συνδέστε τον κωδικό πρόσβασής σας με το σαρωμένο βιομετρικό ή τα διαπιστευτήρια της συσκευής σας για να ξεκλειδώσετε γρήγορα τη βάση δεδομένων σας.</string>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<string name="colorize_password_summary">Colourise password characters by type</string>
|
||||
<string name="content_description_entry_background_color">Entry background colour</string>
|
||||
<string name="invalid_db_sig">Could not recognise the database format.</string>
|
||||
<string name="advanced_unlock_not_recognized">Could not recognise advanced unlock print</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Unable to initialise advanced unlock prompt.</string>
|
||||
<string name="advanced_unlock_not_recognized">Could not recognise device unlock print</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Unable to initialise device unlock prompt.</string>
|
||||
<string name="download_initialization">Initialising…</string>
|
||||
<string name="download_finalization">Finalising…</string>
|
||||
<string name="download_canceled">Cancelled!</string>
|
||||
@@ -33,10 +33,10 @@
|
||||
<string name="encryption">Encryption</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="contribution">Contribution</string>
|
||||
<string name="about_description">password</string>
|
||||
<string name="encryption_algorithm">Encryption</string>
|
||||
<string name="about_description">Android implementation of the KeePass password manager</string>
|
||||
<string name="encryption_algorithm">Encryption algorithm</string>
|
||||
<string name="app_timeout">Timeout</string>
|
||||
<string name="app_timeout_summary">database</string>
|
||||
<string name="app_timeout_summary">Idle time before locking the database</string>
|
||||
<string name="application">App</string>
|
||||
<string name="brackets">Brackets</string>
|
||||
<string name="extended_ASCII">Extended ASCII</string>
|
||||
@@ -236,4 +236,4 @@
|
||||
<string name="list_groups_show_number_entries_title">Show number of entries</string>
|
||||
<string name="show_otp_token_summary">Displays OTP tokens in the list of entries</string>
|
||||
<string name="show_otp_token_title">Show OTP Token</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<string name="hint_conf_pass">Confirmar contraseña</string>
|
||||
<string name="hint_generated_password">Contraseña generada</string>
|
||||
<string name="hint_group_name">Nombre del grupo</string>
|
||||
<string name="hint_keyfile">Cerrojo</string>
|
||||
<string name="hint_keyfile">Archivo de clave</string>
|
||||
<string name="hint_length">Longitud</string>
|
||||
<string name="password">Contraseña</string>
|
||||
<string name="hint_pass">Contraseña</string>
|
||||
@@ -118,7 +118,7 @@
|
||||
<string name="unsupported_db_version">Versión de base de datos incompatible.</string>
|
||||
<string name="uppercase">Mayúsculas</string>
|
||||
<string name="version_label">Versión %1$s</string>
|
||||
<string name="education_unlock_summary">Introduzca la contraseña y/o el cerrojo para desbloquear su base de datos.\n\nRespalde su base de datos en un lugar seguro tras cada cambio.</string>
|
||||
<string name="education_unlock_summary">Introduzca la contraseña y/o el archivo de clave para desbloquear su base de datos.\n\nRespalde su base de datos en un lugar seguro tras cada cambio.</string>
|
||||
<string-array name="list_size_options">
|
||||
<item>Pequeño</item>
|
||||
<item>Mediano</item>
|
||||
@@ -137,7 +137,7 @@
|
||||
<string name="field_value">Valor del campo</string>
|
||||
<string name="file_not_found_content">No se ha podido encontrar el archivo. Intente volver a abrirlo en el explorador de archivos.</string>
|
||||
<string name="invalid_algorithm">Algoritmo incorrecto.</string>
|
||||
<string name="keyfile_is_empty">El cerrojo está vacío.</string>
|
||||
<string name="keyfile_is_empty">El archivo de clave está vacío.</string>
|
||||
<string name="copy_field">Copia de %1$s</string>
|
||||
<string name="menu_form_filling_settings">Rellenado de formularios</string>
|
||||
<string name="protection">Protección</string>
|
||||
@@ -399,7 +399,7 @@
|
||||
<string name="error_copy_group_here">No puede copiar un grupo aquí.</string>
|
||||
<string name="database_data_compression_summary">La compresión de datos reduce el tamaño de la base de datos</string>
|
||||
<string name="database_data_compression_title">Compresión de datos</string>
|
||||
<string name="warning_empty_keyfile">No se recomienda agregar un cerrojo vacío.</string>
|
||||
<string name="warning_empty_keyfile">No se recomienda agregar un archivo de clave vacío.</string>
|
||||
<string name="warning_sure_remove_data">¿Eliminar estos datos de todos modos\?</string>
|
||||
<string name="warning_sure_add_file">¿Agregar el archivo de todos modos\?</string>
|
||||
<string name="warning_replace_file">Al cargar este archivo, se reemplazará el existente.</string>
|
||||
@@ -408,7 +408,7 @@
|
||||
<string name="command_execution">Ejecutando el comando…</string>
|
||||
<string name="hide_broken_locations_summary">Oculta los enlaces rotos en la lista de bases de datos recientes</string>
|
||||
<string name="hide_broken_locations_title">Ocultar enlaces rotos de la base de datos</string>
|
||||
<string name="remember_keyfile_locations_summary">Mantiene seguimiento de dónde los cerrojos son almacenados</string>
|
||||
<string name="remember_keyfile_locations_summary">Mantiene seguimiento de dónde los archivos de claves son almacenados</string>
|
||||
<string name="remember_keyfile_locations_title">Recordar las ubicaciones de los archivos clave</string>
|
||||
<string name="subdomain_search_summary">Busca dominios web con restricciones de subdominios</string>
|
||||
<string name="subdomain_search_title">Búsqueda de subdominio</string>
|
||||
@@ -444,7 +444,6 @@
|
||||
<string name="device_credential">Credenciales del dispositivo</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Teclee la contraseña y luego pulse sobre este botón.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">No se puede inicializar el aviso de desbloqueo avanzado.</string>
|
||||
<string name="advanced_unlock_scanning_error">Error de desbloqueo del dispositivo: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">No se ha podido reconocer la impresión de desbloqueo avanzado</string>
|
||||
<string name="advanced_unlock_invalid_key">No se puede leer la clave de desbloqueo del dispositivo. Por favor, bórrala y repite el procedimiento de reconocimiento del desbloqueo.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extraer la credencial de la base de datos con los datos de desbloqueo del dispositivo</string>
|
||||
@@ -454,7 +453,7 @@
|
||||
<string name="keystore_not_accessible">El almacén de claves no está debidamente inicializado.</string>
|
||||
<string name="biometric_security_update_required">Se requiere actualización de seguridad biométrica.</string>
|
||||
<string name="configure_biometric">No se ha inscrito ninguna credencial biométrica o del dispositivo.</string>
|
||||
<string name="warning_empty_keyfile_explanation">El contenido del cerrojo nunca debe modificarse y, en el mejor de los casos, debe contener datos generados al azar.</string>
|
||||
<string name="warning_empty_keyfile_explanation">El contenido del archivo de clave nunca debe modificarse y, en el mejor de los casos, debe contener datos generados al azar.</string>
|
||||
<string name="warning_empty_recycle_bin">¿Borrar permanentemente todos los nodos de la papelera de reciclaje\?</string>
|
||||
<string name="registration_mode">Modo de registro</string>
|
||||
<string name="save_mode">Modo de guardado</string>
|
||||
@@ -462,7 +461,7 @@
|
||||
<string name="contains_duplicate_uuid_procedure">¿Solucionar el problema generando nuevos UUID para que los duplicados continúen?</string>
|
||||
<string name="menu_keystore_remove_key">Eliminar la clave de desbloqueo del dispositivo</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 renovación</string>
|
||||
<string name="max_history_size_summary">Limitar el tamaño del historial por apunte</string>
|
||||
<string name="max_history_items_summary">Limitar el número de elementos del historial por apunte</string>
|
||||
@@ -639,7 +638,7 @@
|
||||
<string name="screenshot_mode_banner_text">Modo de captura de pantalla</string>
|
||||
<string name="error_hardware_key_unsupported">La llave por hardware no es compatible.</string>
|
||||
<string name="html_about_privacy"><strong>No se recupera ningún dato del usuario</strong>, esta aplicación no se conecta a ningún servidor y funciona solo localmente y respeta plenamente la privacidad de los usuarios.</string>
|
||||
<string name="error_unable_merge_database_kdb">No se puede fusionar con un archivo de base de datos kdb</string>
|
||||
<string name="error_unable_merge_database_kdb">No se puede fusionar con un archivo de base de datos kdb.</string>
|
||||
<string name="error_cancel_by_user">Cancelado por el usuario.</string>
|
||||
<string name="error_no_response_from_challenge">No se puede obtener la respuesta del desafío.</string>
|
||||
<string name="auto_type">Auto-teclear</string>
|
||||
@@ -694,4 +693,4 @@
|
||||
<string name="generate_keyfile">Generar archivo de claves</string>
|
||||
<string name="recursive_number_entries_title">Número recursivo de entradas</string>
|
||||
<string name="hide_templates_summary">Las plantillas no se muestran</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
<string name="error_pass_match">Salasõnad ei klapi.</string>
|
||||
<string name="error_create_database">Andmebaasifaili loomine ei õnnestunud.</string>
|
||||
<string name="entry_url">URL</string>
|
||||
<string name="error_file_not_create">Faili loomine ei õnnestunud</string>
|
||||
<string name="error_file_not_create">Faili loomine ei õnnestunud.</string>
|
||||
<string name="entry_otp">Ühekordne salasõna</string>
|
||||
<string name="clipboard_timeout_summary">Lõikelauale kopeeritud andmete hoidmise aeg (kui sinu seade sellist võimalust toetab)</string>
|
||||
<string name="content_description_keyboard_close_fields">Sulge väljad</string>
|
||||
@@ -234,7 +234,7 @@
|
||||
<string name="menu_delete">Kustuta</string>
|
||||
<string name="menu_paste">Aseta</string>
|
||||
<string name="menu_cancel">Katkesta</string>
|
||||
<string name="error_unable_merge_database_kdb">Mestimine teise kdb andmebaasifailiga ei õnnestu</string>
|
||||
<string name="error_unable_merge_database_kdb">Mestimine teise kdb andmebaasifailiga ei õnnestu.</string>
|
||||
<string name="error_location_unknown">Andmebaasi asukoht pole teada ja toimingut andmebaasiga ei saa teha.</string>
|
||||
<string name="menu_empty_recycle_bin">Tühjenda prügikast</string>
|
||||
<string name="menu_restore_entry_history">Taasta ajalugu</string>
|
||||
@@ -274,7 +274,7 @@
|
||||
<string name="import_app_properties_summary">Rakenduse seadistuste importimiseks vali fail</string>
|
||||
<string name="export_app_properties_title">Ekspordi rakenduse seadistused</string>
|
||||
<string name="success_export_app_properties">Rakenduse seadistused on eksporditud</string>
|
||||
<string name="error_export_app_properties">Rakenduse seadistuste eksportimisel tekkis viga</string>
|
||||
<string name="error_export_app_properties">Rakenduse seadistuste eksportimisel tekkis viga.</string>
|
||||
<string name="root">Juurkaust</string>
|
||||
<string name="memory_usage">Mälukasutus</string>
|
||||
<string name="error_string_type">See tekst ei vasta päringule.</string>
|
||||
@@ -295,7 +295,7 @@
|
||||
<string name="hide_broken_locations_title">Peida katkised andmebaaside lingid</string>
|
||||
<string name="hide_broken_locations_summary">Peida hiljutikasutatud andmebaaside loendist need kirjed, mille lingid enam ei toimi</string>
|
||||
<string name="success_import_app_properties">Rakenduse seadistused on imporditud</string>
|
||||
<string name="error_import_app_properties">Viga rakenduse seadistuste importimisel</string>
|
||||
<string name="error_import_app_properties">Viga rakenduse seadistuste importimisel.</string>
|
||||
<string name="description_app_properties">KeePassDX võimalused rakenduse seadistuste haldamiseks</string>
|
||||
<string name="encryption_explanation">Salasõnalaeka andmebaasi puhul kasutatud krüptoalgoritm</string>
|
||||
<string name="kdf_explanation">Krüptoalgoritmi jaoks võtme loomisel peavõtit muudetakse võtetuletusfunktsiooniga, mis kasutab juhuslikke soolaterakesi.</string>
|
||||
@@ -440,7 +440,6 @@
|
||||
<string name="merge_success">Mestimine õnnestus</string>
|
||||
<string name="biometric_security_update_required">Vajalik on biomeetrilise turvalisuse uuendus.</string>
|
||||
<string name="encrypted_value_stored">Krüptitud salasõna on salvestatud</string>
|
||||
<string name="advanced_unlock_scanning_error">Viga seadme lukustuse eemaldamisel: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Ei õnnestunud tuvastada lukustuse eemaldamiseks vajalikku tunnust</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Seadme lukustuse eemaldamise päringu käivitamine ei õnnestu.</string>
|
||||
<string name="biometric">Biomeetriline</string>
|
||||
@@ -594,7 +593,7 @@
|
||||
<string name="education_field_copy_title">Välja kopeerimine</string>
|
||||
<string name="education_field_copy_summary">Kopeeritud väljade sisu saad asetada kõikjale.\n\nVäljade täitmiseks kasuta meetodist, mis sulle sobib. Ära unusta kopeeritud saalsõna lõikelaualt kustutada.</string>
|
||||
<string name="unavailable">Pole saadaval</string>
|
||||
<string name="error_challenge_already_requested">Autentimispäring on juba esitatud</string>
|
||||
<string name="error_challenge_already_requested">Autentimispäring on juba esitatud.</string>
|
||||
<string name="error_response_already_provided">Autentimisvastus on juba antud.</string>
|
||||
<string name="error_no_response_from_challenge">Autentimisvastust ei õnnestu autentimispäringust tuletada.</string>
|
||||
<string name="warning_database_notification_permission">Teevituste saatmise õigused võimaldavad sulle kuvada andmebaasi olekut ning lukustada seda lihtsalt ligipääsetvast nupust.\n\nKui sa seda õigust ei anna, siis taustal olev andmebaas pole nähtav, kui mõni muu rakendus on parasjagu esiplaanil.</string>
|
||||
@@ -662,8 +661,8 @@
|
||||
<string name="enter">Sisestusklahv (Enter)</string>
|
||||
<string name="education_validate_entry_summary">Ära unusta kirje sisu kontrollida ja salvestada.\n\nKui automaatne lukustus käivitub ja sa unustad, et muutmine oli pooleli, siis võid kaotada oma muudatused.</string>
|
||||
<string name="html_text_ad_free">Erinevalt paljudest teistest salasõnahalduritest, on meie oma <strong>reklaamivaba</strong> ja <strong>avatud lähtekoodiga vaba tarkvara</strong>, mis ei kogu kasutajate isiklikke andmeid oma serveritesse ja seda kõikide versioonide puhul.</string>
|
||||
<string name="html_text_donation"><strong>Aidates kaasa</strong> selle projekti tegevusele <i>(rahaliselt, koodi kirjutades või tõlkides)</i> aitad kogu ettevõtmisel areneda ning soovi korral võid kasutada ka <strong>seda kujundust</strong>.</string>
|
||||
<string name="html_text_donation"><strong>Aidates kaasa</strong> selle projekti tegevusele <i>(rahaliselt, koodi kirjutades või tõlkides)</i>, aitad kogu ettevõtmisel areneda ning soovi korral võid kasutada ka <strong>seda kujundust</strong>.</string>
|
||||
<string name="advanced_unlock_keystore_warning">See funktsionaalsus salvestab krüptitud salasõnad ja kasutajanimed sinu nutiseadme turvalises võtmehoidlas.\n\nSõltuvalt sinu nutiseadme operatsioonisüstemi konkreetsest implementatsioonist, ei pruugi see lahendus siiski täismahus toimida.\n\nPalun kontrolli oma seadme võtmehoidla (KeyStore) ühilduvust ja turvalisust tootjalt ja/või tarkvara loojalt.</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Peale tegevust „Automaatne võtmetoiming“ lülita automaatselt tagasi eelmisele klahvistikule</string>
|
||||
<string name="education_read_only_summary">Saad juhtida sessioonil kasutatavat avamisviisi.\n\n„Kirjutuskaitstud“ tagab, et juhuslike muudatustega ei läheks andmeid kaotsi.\n„Muudetav“ võimaldab sul lisada, kustutada või muuta kõiki andmebaasi kirjeid.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -489,7 +489,6 @@
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Zure kutxa gotorraren pasahitz-nagusia gogoratu behar duzu naiz eta desblokeo aurreratuko ezagutzea erabili arren.</string>
|
||||
<string name="encrypted_value_stored">Zifratutako pasahitza gordeta</string>
|
||||
<string name="unavailable">Datu-base honek ez du biltegiratuta kredentzialik.</string>
|
||||
<string name="advanced_unlock_scanning_error">Gailuaren desblokeatze errorea: %1$s</string>
|
||||
<string name="menu_appearance_settings">Itxura</string>
|
||||
<string name="autofill_sign_in_prompt">KeePassDXekin erregistratu</string>
|
||||
<string name="autofill_explanation_summary">Gaitu betetze automatikoa beste aplikazioetako formularioak errez betetzeko</string>
|
||||
@@ -683,4 +682,4 @@
|
||||
<string name="ask">Galdetu</string>
|
||||
<string name="configure">Konfiguratu</string>
|
||||
<string name="unlock">Desblokeatu</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -310,4 +310,4 @@
|
||||
<string name="success_import_app_properties">Na-import ang mga setting ng app</string>
|
||||
<string name="error_import_app_properties">Error habang nagi-import ng mga setting ng app.</string>
|
||||
<string name="success_export_app_properties">Na-export ang mga setting ng app</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -509,7 +509,6 @@
|
||||
<string name="device_credential">Identifiant de l\'appareil</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Tapez le mot de passe, puis cliquez sur ce bouton.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Impossible d\'initialiser l\'invite de déverrouillage avancé.</string>
|
||||
<string name="advanced_unlock_scanning_error">Erreur de déverrouillage avancé : %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Impossible de reconnaître l\'empreinte de déverrouillage de l\'appareil</string>
|
||||
<string name="advanced_unlock_invalid_key">Impossible de lire la clé de déverrouillage de l\'appareil. Veuillez la supprimer et répéter la procédure de reconnaissance de déverrouillage.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extraire les identifiants de la base de données avec des données de déverrouillage de l\'appareil</string>
|
||||
@@ -704,6 +703,6 @@
|
||||
<string name="recursive_number_entries_title">Nombre d\'entrées récursif</string>
|
||||
<string name="recursive_number_entries_summary">Calcule récursivement le nombre d\'entrées dans un groupe</string>
|
||||
<string name="warning_large_keyfile">Il n\'est pas recommandé d\'ajouter un fichier clé volumineux, car cela pourrait empêcher l\'ouverture de la base de données.</string>
|
||||
<string name="hide_templates_title">Cacher les modèles</string>
|
||||
<string name="hide_templates_summary">Les modèles ne sont pas affichés</string>
|
||||
</resources>
|
||||
<string name="hide_templates_title">Cacher les gabarits</string>
|
||||
<string name="hide_templates_summary">Les gabarits ne sont pas affichés</string>
|
||||
</resources>
|
||||
|
||||
@@ -425,7 +425,6 @@
|
||||
<string name="advanced_unlock_invalid_key">Non foi posíbel ler a clave de desbloqueo avanzado. Por favor, bórrea e repita o procedemento de recoñecemento do desbloqueo.</string>
|
||||
<string name="encrypted_value_stored">Contrasinal cifrado almacenado</string>
|
||||
<string name="advanced_unlock_not_recognized">Non foi posíbel recoñecer a pegada do desbloqueo avanzado</string>
|
||||
<string name="advanced_unlock_scanning_error">Erro de desbloqueo avanzado: %1$s</string>
|
||||
<string name="autofill_service_name">Servizo de autocompletado do KeePassDX</string>
|
||||
<string name="database_history">Historial</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Aínda precisa lembrar a súa credencial principal se usar o recoñecemento de desbloqueo avanzado.</string>
|
||||
@@ -662,4 +661,4 @@
|
||||
<string name="html_text_dev_feature_buy_pro">Ao comprar a versión <strong>pro</strong>,</string>
|
||||
<string name="html_text_dev_feature_encourage">está a incentivar os programadores a crear <strong>novas funcionalidades</strong> e a <strong>corrixir erros</strong> reportados.</string>
|
||||
<string name="merge_success">A fusión completouse correctamente</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -490,7 +490,6 @@
|
||||
<string name="menu_keystore_remove_key">Izbriši ključ za otključavanje uređaja</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Poveznica za otključavanje uređaja</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Nije moguće pokrenuti prozor za otključavanje uređaja.</string>
|
||||
<string name="advanced_unlock_scanning_error">Greška otključavanja uređaja: %1$s</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Izdvoji podatake za prijavu na bazu podataka pomoću podataka za otključavanje uređaja</string>
|
||||
<string name="advanced_unlock_not_recognized">Nije bilo moguće prepoznati ispis za otključavanje uređaja</string>
|
||||
<string name="advanced_unlock_invalid_key">Nije moguće pročitati ključ za otključavanje uređaja. Izbriši ga i ponovi postupak prepoznavanja otključavanja.</string>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<string name="html_text_ad_free">Számos más jelszókezelő alkalmazással ellentétben, ez egy <strong>reklámmentes</strong>, <strong>copyleft licencelésű szabad szoftver</strong>, amely nem gyűjt személyes adatokat a kiszolgálókon, bármelyik verziót is használja.</string>
|
||||
<string name="html_text_buy_pro">A pro verzió megvásárlásával hozzáférést kap ehhez a <strong>vizuális stílushoz</strong>, és segít a <strong>közösségi projektek megvalósulásában.</strong></string>
|
||||
<string name="html_text_feature_generosity">Ez a <strong>vizuális stílus</strong> az Ön nagylelkűségének köszönhetően érhető el.</string>
|
||||
<string name="html_text_donation">Azzal, hogy <strong>hozzájárul</strong> a projekthez <i>(pénzzel, kóddal, fordítással)</i>, segít abban, hogy a projekt tovább éljen és gyarapodjon, továbbá Ön jogosulttá válik a <strong>téma</strong> feloldási eljárásra is.</string>
|
||||
<string name="html_text_donation">Azzal, hogy <strong>hozzájárul</strong> a projekthez <i>(pénzzel, kóddal, fordítással)</i>, segít abban, hogy a projekt tovább éljen és gyarapodjon, továbbá Ön jogosulttá válik a <strong>téma</strong>feloldásának lehetősége.</string>
|
||||
<string name="html_text_dev_feature">Ez a funkció <strong>fejlesztés alatt áll</strong>, és az Ön <strong>támogatására</strong> van szükség, hogy hamarosan elérhető legyen.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">A <strong>pro</strong> verzió megvásárlásával,</string>
|
||||
<string name="html_text_dev_feature_contibute">A <strong>támogatással</strong></string>
|
||||
@@ -537,7 +537,6 @@
|
||||
<string name="credential_before_click_advanced_unlock_button">Írja be a jelszót, majd kattintson erre a gombra.</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Ideiglenes eszközfeloldás</string>
|
||||
<string name="permission">Engedély</string>
|
||||
<string name="advanced_unlock_scanning_error">Eszközfeloldási hiba: %1$s</string>
|
||||
<string name="content">Tartalom</string>
|
||||
<string name="advanced_unlock_tap_delete">Koppintson az eszközfeloldási kulcsok törléséhez</string>
|
||||
<string name="device_credential_unlock_enable_title">Eszköz hitelesítő adataival történő feloldás</string>
|
||||
@@ -708,4 +707,4 @@
|
||||
<string name="hide_templates_summary">A sablonok nem jelennek meg</string>
|
||||
<string name="generate_keyfile">Kulcsfájl előállítása</string>
|
||||
<string name="hide_templates_title">Sablonok elrejtése</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -582,7 +582,6 @@
|
||||
<string name="upper_case">HURUF BESAR</string>
|
||||
<string name="title_case">Huruf Judul</string>
|
||||
<string name="character_count">Jumlah karakter: %1$d</string>
|
||||
<string name="advanced_unlock_scanning_error">Terjadi kesalahan buka kunci perangkat: %1$s</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Tidak dapat menginisialisasi perintah buka kunci perangkat.</string>
|
||||
<string name="monospace_font_fields_enable_title">Bidang tipe huruf</string>
|
||||
<string name="keyboard_save_search_info_title">Simpan info terbagi</string>
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
<string name="html_text_ad_free">Diversamente da molte app di gestione password, questa è <strong>senza pubblicità</strong>, <strong>software libero (copyleft)</strong> e non raccoglie dati personali nei suoi server, non importa quale versione usi.</string>
|
||||
<string name="html_text_buy_pro">Acquistando la versione pro, avrai accesso a questo <strong>stile visivo</strong> e soprattutto aiuterai nella <strong>realizzazione dei progetti della comunità.</strong></string>
|
||||
<string name="html_text_feature_generosity">Questo <strong>stile visivo</strong> è disponibile grazie alla tua generosità.</string>
|
||||
<string name="html_text_donation">Se <strong>contribuirai</strong> al progretto <i>(tramite una donazione, del codice una traduzione)</i>, lo aiuterai a vivere e a prosperare, e avrai a disposizione anche la procedura di sblocco del <strong>tema</strong>.</string>
|
||||
<string name="html_text_donation">Se <strong>contribuirai</strong>al progretto <i>(tramite una donazione, del codice, una traduzione)</i>, lo aiuterai a vivere e a prosperare, e avrai a disposizione anche la procedura di sblocco del <strong>tema</strong>.</string>
|
||||
<string name="html_text_dev_feature">Questa funzione è <strong>in sviluppo</strong> e richiede il tuo <strong>contributo</strong> per essere disponibile a breve.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Acquistando la versione <strong>pro</strong>,</string>
|
||||
<string name="html_text_dev_feature_contibute">
|
||||
@@ -515,7 +515,6 @@
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Collegamento allo sblocco con dispositivo</string>
|
||||
<string name="device_credential">Credenziali del dispositivo</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Inserisci la password, poi clicca questo pulsante.</string>
|
||||
<string name="advanced_unlock_scanning_error">Errore sblocco con dispositivo: %1$s</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Riconoscimento sblocco con dispositivo</string>
|
||||
<string name="menu_keystore_remove_key">Elimina chiave di sblocco del dispositivo</string>
|
||||
<string name="error_rebuild_list">Non è possibile ricostruire la lista correttamente.</string>
|
||||
@@ -711,4 +710,4 @@
|
||||
<string name="hide_templates_title">Nascondi template</string>
|
||||
<string name="recursive_number_entries_summary">Calcola in modo ricorsivo il numero di voci in un gruppo</string>
|
||||
<string name="hide_templates_summary">I template non vengono mostrati</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<string name="entry_user_name">שם משתמש</string>
|
||||
<string name="error_arc4">הצופן זרם Arcfour אינו נתמך.</string>
|
||||
<string name="error_can_not_handle_uri">לא ניתן לטפל ב-URI הזה ב-KeePassDX.</string>
|
||||
<string name="error_file_not_create">לא ניתן ליצור קובץ</string>
|
||||
<string name="error_file_not_create">לא ניתן ליצור קובץ.</string>
|
||||
<string name="error_invalid_db">לא ניתן לקרוא את מסד הנתונים.</string>
|
||||
<string name="error_invalid_path">ודא שהנתיב נכון.</string>
|
||||
<string name="error_no_name">הזן שם.</string>
|
||||
@@ -190,7 +190,7 @@
|
||||
<string name="error_copy_entry_here">אינך יכול להעתיק רשומה לכאן.</string>
|
||||
<string name="error_otp_counter">המונה חייב להיות בין %1$d ל-%2$d.</string>
|
||||
<string name="error_otp_period">פרק זמן חייב להיות בין %1$d ל-%2$d שניות.</string>
|
||||
<string name="error_registration_read_only">שמירת פריט חדש אינה מותרת במסד נתונים לקריאה בלבד</string>
|
||||
<string name="error_registration_read_only">שמירת פריט חדש אינה מותרת במסד נתונים לקריאה בלבד.</string>
|
||||
<string name="error_string_type">טקסט זה אינו תואם את הפריט המבוקש.</string>
|
||||
<string name="error_field_name_already_exists">שם השדה כבר קיים.</string>
|
||||
<string name="error_database_uri_null">לא ניתן לאחזר URI של מסד הנתונים.</string>
|
||||
@@ -203,7 +203,7 @@
|
||||
<string name="error_cancel_by_user">בוטל על ידי המשתמש.</string>
|
||||
<string name="error_driver_required">נדרש מנהל התקן עבור %1$s.</string>
|
||||
<string name="error_location_unknown">מיקום מסד הנתונים אינו ידוע, לא ניתן לבצע פעולת מסד נתונים.</string>
|
||||
<string name="error_unable_merge_database_kdb">לא ניתן למזג עם קובץ מסד נתונים kdb</string>
|
||||
<string name="error_unable_merge_database_kdb">לא ניתן למזג עם קובץ מסד נתונים kdb.</string>
|
||||
<string name="error_hardware_key_unsupported">מפתח חומרה אינו נתמך.</string>
|
||||
<string name="error_empty_key">המפתח לא יכול להיות ריק.</string>
|
||||
<string name="file_not_found_content">לא ניתן למצוא את הקובץ. נסה לפתוח אותו מחדש מסייר הקבצים שלך.</string>
|
||||
@@ -539,7 +539,6 @@
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">חלץ אישור מסד נתונים עם נתוני ביטול נעילת מכשיר</string>
|
||||
<string name="advanced_unlock_invalid_key">לא ניתן לקרוא את מפתח ביטול נעילת המכשיר. נא למחוק אותו ולחזור על התהליך לזיהוי ביטול נעילה.</string>
|
||||
<string name="advanced_unlock_not_recognized">לא היה ניתן לזהות טביעת ביטול נעילת מכשיר</string>
|
||||
<string name="advanced_unlock_scanning_error">שגיאת ביטול נעילת מכשיר: %1$s</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">הקלד את הסיסמה, ואז לחץ על הכפתור הזה.</string>
|
||||
<string name="lock_database_show_button_title">הצג כפתור נעילה</string>
|
||||
<string name="lock_database_show_button_summary">הצג את כפתור הנעילה בממשק המשתמש</string>
|
||||
@@ -579,7 +578,7 @@
|
||||
<string name="success_import_app_properties">הגדרות יישום יובאו</string>
|
||||
<string name="sort_title">כותרת</string>
|
||||
<string name="description_app_properties">מאפייני KeePassDX לניהול הגדרות יישום</string>
|
||||
<string name="error_import_app_properties">שגיאה במהלך ייבוא הגדרות יישום</string>
|
||||
<string name="error_import_app_properties">שגיאה במהלך ייבוא הגדרות יישום.</string>
|
||||
<string name="style_name_divine">שמיימי</string>
|
||||
<string name="style_name_classic">קלאסי</string>
|
||||
<string name="style_name_kunzite">Kunzite</string>
|
||||
@@ -639,7 +638,7 @@
|
||||
<string name="import_app_properties_title">ייבוא הגדרות יישום</string>
|
||||
<string name="export_app_properties_title">ייצוא הגדרות יישום</string>
|
||||
<string name="export_app_properties_summary">צור קובץ כדי לייצא הגדרות יישום</string>
|
||||
<string name="error_export_app_properties">שגיאה במהלך ייצוא הגדרות יישום</string>
|
||||
<string name="error_export_app_properties">שגיאה במהלך ייצוא הגדרות יישום.</string>
|
||||
<string name="html_text_feature_generosity"><strong>הסגנון החזותי</strong> הזה זמין הודות לנדיבות שלך.</string>
|
||||
<string name="html_text_dev_feature_contibute">על ידי <strong>תרומה</strong>,</string>
|
||||
<string name="hide_templates_title">הסתר תבניות</string>
|
||||
|
||||
@@ -491,7 +491,6 @@
|
||||
<string name="device_credential">デバイス認証情報</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">パスワードを入力し、このボタンをタップします。</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">デバイスのロック解除プロンプトを初期化できません。</string>
|
||||
<string name="advanced_unlock_scanning_error">デバイスのロック解除エラー: %1$s</string>
|
||||
<string name="advanced_unlock_invalid_key">デバイスのロック解除キーを読み取ることができません。削除して、ロック解除認識手順を繰り返してください。</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">デバイスのロック解除データを使用してデータベースの資格情報を抽出する</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">デバイスのロック解除認識</string>
|
||||
@@ -662,7 +661,7 @@
|
||||
<string name="waiting_challenge_request">チャレンジリクエストを待っています…</string>
|
||||
<string name="error_response_already_provided">回答はすでに提供されています。</string>
|
||||
<string name="error_no_response_from_challenge">チャレンジからの応答を取得できません。</string>
|
||||
<string name="error_unable_merge_database_kdb">kdbデータベースファイルとのマージはできません</string>
|
||||
<string name="error_unable_merge_database_kdb">kdbデータベースファイルとのマージはできません。</string>
|
||||
<string name="menu_advanced_unlock_settings_summary">生体認証、デバイス認証</string>
|
||||
<string name="menu_database_settings_summary">メタデータ、ごみ箱、テンプレート、履歴</string>
|
||||
<string name="menu_security_settings_summary">暗号化、鍵導出関数</string>
|
||||
@@ -673,7 +672,7 @@
|
||||
<string name="warning_database_notification_permission">通知権限を使用すると、データベースのステータスを表示し、簡単にアクセスできるボタンでロックすることができます。
|
||||
\n
|
||||
\nこの権限を有効にしないと、別のアプリケーションがフォアグラウンドにある場合、バックグラウンドで開いているデータベースは表示されません。</string>
|
||||
<string name="ask">話す</string>
|
||||
<string name="ask">権限を要求する</string>
|
||||
<string name="merge_success">マージが正常に完了しました</string>
|
||||
<string name="configure">設定する</string>
|
||||
<string name="menu_appearance_settings_summary">テーマ、色、属性</string>
|
||||
@@ -699,4 +698,4 @@
|
||||
<string name="style_name_kunzite">クンツァイト</string>
|
||||
<string name="hide_templates_title">テンプレートを非表示にする</string>
|
||||
<string name="hide_templates_summary">テンプレートは表示されません</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -395,7 +395,6 @@
|
||||
<string name="configure">Konfigūruoti</string>
|
||||
<string name="biometric_security_update_required">Reikalingas biometrinių duomenų saugumo atnaujinimas.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Jei naudojate įrenginio atrakinimo atpažinimą, vis tiek turite prisiminti pagrindinį saugyklos raktą</string>
|
||||
<string name="advanced_unlock_scanning_error">Įrenginio atrakinimo klaida: %1$s</string>
|
||||
<string name="database_history">Istorija</string>
|
||||
<string name="properties">Nustatymai</string>
|
||||
<string name="menu_appearance_settings_summary">Temos, spalvos, atributai</string>
|
||||
|
||||
38
app/src/main/res/values-mk/strings.xml
Normal file
38
app/src/main/res/values-mk/strings.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="contact">Контакт</string>
|
||||
<string name="info">Инфо:</string>
|
||||
<string name="contribution">Придонес</string>
|
||||
<string name="feedback">Повратни информации</string>
|
||||
<string name="homepage">Дома</string>
|
||||
<string name="about_description">Имплементација на менаџерот за лозинки KeePass за Android</string>
|
||||
<string name="accept">Прифати</string>
|
||||
<string name="edit_entry">Уреди запис</string>
|
||||
<string name="add_group">Додади група</string>
|
||||
<string name="master_key">Главен клуч</string>
|
||||
<string name="security">Безбедност</string>
|
||||
<string name="encryption">Енкрипција</string>
|
||||
<string name="encryption_algorithm">Енкрипциски алгоритам</string>
|
||||
<string name="key_derivation_function">Функција за изведување на клучеви</string>
|
||||
<string name="app_timeout">Истекување на времето</string>
|
||||
<string name="app_timeout_summary">Време на мирување пред заклучување на базата</string>
|
||||
<string name="application">Апликација</string>
|
||||
<string name="brackets">Загради</string>
|
||||
<string name="extended_ASCII">Проширен ASCII</string>
|
||||
<string name="file_manager_install_description">За креирање, отворање и зачувување на датотеки од базата е потребен менаџер на датотеки што ги прифаќа ACTION_CREATE_DOCUMENT и ACTION_OPEN_DOCUMENT.</string>
|
||||
<string name="allow">Дозволи</string>
|
||||
<string name="clipboard_cleared">Clipboard исчистенa</string>
|
||||
<string name="clipboard_error_title">Clipboard проблем</string>
|
||||
<string name="clipboard_error">Некои уреди не дозволуваат апликациите да ја користат clipboard.</string>
|
||||
<string name="clipboard_error_clear">Не може да се исчисти clipboard</string>
|
||||
<string name="clipboard_timeout">Clipboard тајмаут</string>
|
||||
<string name="clipboard_timeout_summary">Времетраење на складирањето во clipboard (доколку е поддржано од вашиот уред)</string>
|
||||
<string name="content_description_background">Позадина</string>
|
||||
<string name="content_description_open_file">Отвори датотека</string>
|
||||
<string name="content_description_node_children">Поддеца</string>
|
||||
<string name="content_description_add_node">Додај јазол</string>
|
||||
<string name="content_description_add_entry">Додај запис</string>
|
||||
<string name="content_description_add_group">Додади група</string>
|
||||
<string name="content_description_add_item">Додај ставка</string>
|
||||
<string name="content_description_file_information">Информации за датотеката</string>
|
||||
</resources>
|
||||
@@ -405,7 +405,6 @@
|
||||
<string name="hide_broken_locations_summary">Skjul ødelagte lenker i listen over nylige databaser</string>
|
||||
<string name="hide_broken_locations_title">Skjul ødelagte databaselenker</string>
|
||||
<string name="autofill_ask_to_save_data_title">Spør om lagring av data</string>
|
||||
<string name="advanced_unlock_scanning_error">Feil ved opplåsing: %1$s</string>
|
||||
<string name="warning_empty_keyfile">Det anbefales ikke å legge til en tom nøkkelfil.</string>
|
||||
<string name="warning_sure_add_file">Legg til filen uansett\?</string>
|
||||
<string name="registration_mode">Registreringsmodus</string>
|
||||
@@ -696,4 +695,4 @@
|
||||
<string name="style_name_follow_system">Følg systemet</string>
|
||||
<string name="style_name_light">Lyst</string>
|
||||
<string name="style_name_dark">Mørkt</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
<string name="biometric_unlock_enable_title">Ontgrendelen met biometrie</string>
|
||||
<string name="biometric_unlock_enable_summary">Gebruik biometrische herkenning om de database te openen</string>
|
||||
<string name="biometric_delete_all_key_title">Coderingssleutels verwijderen</string>
|
||||
<string name="biometric_delete_all_key_summary">Alle coderingssleutels met betrekking tot apparaat-ontgrendeling verwijderen</string>
|
||||
<string name="biometric_delete_all_key_summary">Alle coderingssleutels met betrekking tot apparaatontgrendeling verwijderen</string>
|
||||
<string name="unavailable_feature_text">Kan deze functie niet starten.</string>
|
||||
<string name="unavailable_feature_version">Dit apparaat draait op Android %1$s, maar %2$s of hoger is vereist.</string>
|
||||
<string name="unavailable_feature_hardware">De bijbehorende hardware werd niet gevonden.</string>
|
||||
@@ -272,7 +272,7 @@
|
||||
<string name="html_text_ad_free">In tegenstelling tot veel apps voor wachtwoordbeheer, is deze <strong> reclamevrij</strong>, <strong> vrije software </strong> en verzamelt het geen persoonlijke gegevens op haar servers, ongeacht de versie die je gebruikt.</string>
|
||||
<string name="html_text_buy_pro">Door de pro-versie te kopen krijg je toegang tot dit <strong>visuele thema</strong> en draag je bij aan het <strong>realiseren van gemeenschapsprojecten.</strong></string>
|
||||
<string name="html_text_feature_generosity">Dit <strong>visuele thema</strong> is beschikbaar gemaakt dankzij jouw vrijgevigheid.</string>
|
||||
<string name="html_text_donation">Met een <strong>bijdrage</strong> <i>(financieel, code, vertaling),</i> help je dit project in het voortbestaan en verdere groei. Bovendien kom je in aanmerking ook de <strong>procedure</strong> waarmee je de extra thema\'s kunt ontgrendelen.</string>
|
||||
<string name="html_text_donation">Door <strong>bij te dragen</strong> aan het project <i>(financieel, code, vertaling)</i>, help je het om te blijven leven en bloeien en kom je bovendien in aanmerking voor het <strong>thema</strong> ontgrendelingsprocedure.</string>
|
||||
<string name="html_text_dev_feature">Deze functie <strong>wordt momenteel ontwikkeld</strong> en kan alleen beschikbaar komen middels jouw <strong>bijdrage</strong>.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Door de <strong>pro</strong>-versie te kopen,</string>
|
||||
<string name="html_text_dev_feature_contibute">Door <strong>bij te dragen</strong>,</string>
|
||||
@@ -342,7 +342,7 @@
|
||||
<string name="menu_advanced_unlock_settings">Apparaatontgrendeling</string>
|
||||
<string name="biometric">Biometrie</string>
|
||||
<string name="biometric_auto_open_prompt_title">Auto-open suggestie</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatisch om apparaat-ontgrendeling vragen als een database hiervoor is ingesteld</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatisch om apparaatontgrendeling vragen als een database hiervoor is ingesteld</string>
|
||||
<string name="enable">Inschakelen</string>
|
||||
<string name="disable">Uitschakelen</string>
|
||||
<string name="master_key">Hoofdsleutel</string>
|
||||
@@ -371,7 +371,7 @@
|
||||
<string name="contains_duplicate_uuid_procedure">Probleem oplossen door nieuwe UUID\'s te genereren voor de duplicaten\?</string>
|
||||
<string name="database_opened">Database geopend</string>
|
||||
<string name="clipboard_explanation_summary">Kopieer velden met behulp van het klembord van dit apparaat</string>
|
||||
<string name="advanced_unlock_explanation_summary">Apparaat-ontgrendeling gebruiken om een database eenvoudiger te openen</string>
|
||||
<string name="advanced_unlock_explanation_summary">Apparaatontgrendeling gebruiken om een database eenvoudiger te openen</string>
|
||||
<string name="database_data_compression_title">Gegevenscompressie</string>
|
||||
<string name="database_data_compression_summary">Gegevenscompressie verkleint de omvang van de database</string>
|
||||
<string name="max_history_items_title">Maximum aantal</string>
|
||||
@@ -489,33 +489,32 @@
|
||||
<string name="search_mode">Zoekmodus</string>
|
||||
<string name="error_registration_read_only">Het opslaan van een nieuw item is niet toegestaan in een alleen-lezen database.</string>
|
||||
<string name="education_advanced_unlock_summary">Koppel je wachtwoord aan je gescande biometrische gegevens of apparaatreferentie om je database snel te ontgrendelen.</string>
|
||||
<string name="education_advanced_unlock_title">Apparaat-ontgrendeling database</string>
|
||||
<string name="education_advanced_unlock_title">Ontgrendeling database op apparaat</string>
|
||||
<string name="enter">Enter</string>
|
||||
<string name="backspace">Backspace</string>
|
||||
<string name="select_entry">Item selecteren</string>
|
||||
<string name="back_to_previous_keyboard">Terug naar vorig toetsenbord</string>
|
||||
<string name="custom_fields">Aangepaste velden</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Alle coderingssleutels met betrekking tot apparaat-ontgrendeling verwijderen?</string>
|
||||
<string name="advanced_unlock_timeout">Time-out bij apparaat-ontgrendeling</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Duur van apparaat-ontgrendeling voordat de inhoud wordt verwijderd</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Vervaltijd voor apparaat-ontgrendeling</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Sla geen versleutelde inhoud op om apparaat-ontgrendeling te gebruiken</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Tijdelijke apparaat-ontgrendeling</string>
|
||||
<string name="advanced_unlock_timeout">Time-out bij apparaatontgrendeling</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Duur van apparaatontgrendeling voordat de inhoud wordt verwijderd</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Vervaltijd voor apparaatontgrendeling</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Sla geen versleutelde inhoud op om apparaatontgrendeling te gebruiken</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Tijdelijke apparaatontgrendeling</string>
|
||||
<string name="device_credential_unlock_enable_summary">Hiermee kan je de referentie van je apparaat gebruiken om de database te openen</string>
|
||||
<string name="device_credential_unlock_enable_title">Ontgrendeling met apparaatreferenties</string>
|
||||
<string name="advanced_unlock_tap_delete">Tik om apparaat-ontgrendelingssleutels te verwijderen</string>
|
||||
<string name="advanced_unlock_tap_delete">Tik om sleutels voor apparaatontgrendeling te verwijderen</string>
|
||||
<string name="content">Inhoud</string>
|
||||
<string name="device_credential">Apparaatreferentie</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Typ het wachtwoord en klik vervolgens op deze knop.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Kan apparaat-ontgrendelingsprompt niet initialiseren.</string>
|
||||
<string name="advanced_unlock_scanning_error">Apparaat-ontgrendelingsfout: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Kan apparaat-ontgrendelingsafdruk niet herkennen</string>
|
||||
<string name="advanced_unlock_invalid_key">Kan de apparaat-ontgrendelingssleutel niet lezen. Verwijder deze en herhaal de herkenningsprocedure voor het ontgrendelen.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Databasegegevens uitpakken met apparaat-ontgrendelingsgegevens</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Apparaat-ontgrendeling</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Je moet nog steeds je hoofdwachtwoord onthouden als je apparaat-ontgrendelingsherkenning gebruikt.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Koppeling naar Apparaat-ontgrendeling</string>
|
||||
<string name="menu_keystore_remove_key">Apparaat-ontgrendelingssleutel verwijderen</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Kan apparaatontgrendeling niet initialiseren.</string>
|
||||
<string name="advanced_unlock_not_recognized">Vingerafdruk niet herkent bij apparaatontgrendeling</string>
|
||||
<string name="advanced_unlock_invalid_key">Kan de sleutel voor apparaatontgrendeling niet lezen. Verwijder deze en herhaal de herkenningsprocedure voor het ontgrendelen.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Database uitpakken met gegevens voor apparaatontgrendeling</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Apparaatontgrendeling</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Je moet nog steeds je hoofdwachtwoord onthouden als je apparaatontgrendeling gebruikt.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Koppeling naar apparaatontgrendeling</string>
|
||||
<string name="menu_keystore_remove_key">Sleutel voor apparaatontgrendeling verwijderen</string>
|
||||
<string name="error_field_name_already_exists">De veldnaam bestaat al.</string>
|
||||
<string name="unit_gibibyte">GiB</string>
|
||||
<string name="unit_mebibyte">MiB</string>
|
||||
@@ -685,7 +684,7 @@
|
||||
<string name="warning_database_notification_permission">Met de meldingstoestemming kunt u de status van de database weergeven en vergrendelen met een gemakkelijk toegankelijke knop.
|
||||
\n
|
||||
\nAls u deze toestemming niet verleent, is de database die op de achtergrond is geopend niet zichtbaar als er een andere applicatie op de voorgrond staat.</string>
|
||||
<string name="unlock_and_link_biometric">Apparaat-ontgrendelingslink</string>
|
||||
<string name="unlock_and_link_biometric">Koppeling naar apparaatontgrendeling</string>
|
||||
<string name="education_validate_entry_summary">Vergeet niet om de invoer te valideren en de database op te slaan.
|
||||
\n
|
||||
\nWanneer automatische vergrendeling is geactiveerd en u vergeet dat u een wijziging aan het aanbrengen was, dan loopt u het risico gegevens te verliezen.</string>
|
||||
@@ -710,4 +709,4 @@
|
||||
<string name="recursive_number_entries_title">Recursief aantal items</string>
|
||||
<string name="recursive_number_entries_summary">Berekent recursief het aantal items in een groep</string>
|
||||
<string name="warning_large_keyfile">Het is niet verstandig om een groot sleutelbestand toe te voegen, aangezien dit het openen van de database kan belemmeren.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
<string name="autofill_application_id_blocklist_title">Lista zablokowanych aplikacji</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Automatycznie przełącz się z powrotem na poprzednią klawiaturę po wykonaniu automatycznej akcji klawiszy</string>
|
||||
<string name="keyboard_previous_fill_in_title">Przełącz się z powrotem</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Automatycznie przełącz się z powrotem do poprzedniej klawiatury na ekranie poświadczeń bazy danych</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Automatycznie przełącz się z powrotem do poprzedniej klawiatury na ekranie poświadczeń bazy danych</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Ekran poświadczeń bazy danych</string>
|
||||
<string name="keyboard_change">Przełącz klawiaturę</string>
|
||||
<string name="upload_attachment">Prześlij %1$s</string>
|
||||
@@ -511,7 +511,6 @@
|
||||
<string name="advanced_unlock_tap_delete">Stuknij, aby usunąć klucze odblokowywania urządzenia</string>
|
||||
<string name="content">Zawartość</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Rozpoznawanie odblokowania urządzenia</string>
|
||||
<string name="advanced_unlock_scanning_error">Błąd odblokowania urządzenia: %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>
|
||||
@@ -654,7 +653,7 @@
|
||||
<string name="error_hardware_key_unsupported">Klucz sprzętowy nie jest obsługiwany.</string>
|
||||
<string name="error_empty_key">Klucz nie może być pusty.</string>
|
||||
<string name="corrupted_file">Uszkodzony plik.</string>
|
||||
<string name="remember_hardware_key_title">Zapamiętaj klawisze sprzętowe</string>
|
||||
<string name="remember_hardware_key_title">Zapamiętaj klucze sprzętowe</string>
|
||||
<string name="remember_hardware_key_summary">Śledzi używane klucze sprzętowe</string>
|
||||
<string name="enable_screenshot_mode_title">Tryb zrzutu ekranu</string>
|
||||
<string name="enable_screenshot_mode_summary">Zezwól aplikacjom innych dostawców na nagrywanie lub robienie zrzutów ekranu aplikacji</string>
|
||||
@@ -707,4 +706,4 @@
|
||||
<string name="recursive_number_entries_title">Rekurencyjna liczba wpisów</string>
|
||||
<string name="hide_templates_title">Ukryj szablony</string>
|
||||
<string name="hide_templates_summary">Szablony nie są wyświetlane</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -512,7 +512,6 @@
|
||||
<string name="properties">Propriedades</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Digite a senha e clique neste botão.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Não foi possível inicializar o prompt de desbloqueio do dispositivo.</string>
|
||||
<string name="advanced_unlock_scanning_error">Erro de desbloqueio do dispositivo: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Não foi possível reconhecer a impressão de desbloqueio</string>
|
||||
<string name="advanced_unlock_invalid_key">Não é possível ler a chave de desbloqueio do dispositivo. Exclua-o e repita o procedimento de reconhecimento de desbloqueio.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extraia a credencial do banco de dados com os dados de desbloqueio do dispositivo</string>
|
||||
|
||||
@@ -468,7 +468,6 @@
|
||||
<string name="device_credential">Credencial do dispositivo</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Digite a palavra-passe e depois clique neste botão.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Não foi possível inicializar a solicitação de desbloqueio do dispositivo.</string>
|
||||
<string name="advanced_unlock_scanning_error">Erro de desbloqueio do dispositivo: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Não foi possível reconhecer a impressão de desbloqueio do dispositivo</string>
|
||||
<string name="advanced_unlock_invalid_key">Não é possível ler a chave de desbloqueio do dispositivo. Elimine-a e repita o procedimento de reconhecimento de desbloqueio.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extrair credencial da base de dados com dados de desbloqueio do dispositivo</string>
|
||||
|
||||
@@ -446,7 +446,6 @@
|
||||
<string name="accept">Aceitar</string>
|
||||
<string name="device_credential">Credencial do dispositivo</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Não foi possível inicializar a solicitação de desbloqueio do dispositivo.</string>
|
||||
<string name="advanced_unlock_scanning_error">Erro de desbloqueio do dispositivo: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Não foi possível reconhecer a impressão de desbloqueio do dispositivo</string>
|
||||
<string name="advanced_unlock_invalid_key">Não é possível ler a chave de desbloqueio do dispositivo. Elimine-a e repita o procedimento de reconhecimento de desbloqueio.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extrair credencial da base de dados com dados de desbloqueio do dispositivo</string>
|
||||
|
||||
@@ -597,7 +597,6 @@
|
||||
<string name="warning_copy_permission">Permisiunea de notificare este necesară pentru a utiliza funcția de notificare a clipboardului.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Legătură la deblocarea dispozitivului</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Recunoașterea deblocării dispozitivului</string>
|
||||
<string name="advanced_unlock_scanning_error">Eroare de deblocare a dispozitivului: %1$s</string>
|
||||
<string name="unlock_and_link_biometric">Legătură de deblocare a dispozitivului</string>
|
||||
<string name="configure_biometric">Nu se înrolează nicio credențială biometrică sau de dispozitiv.</string>
|
||||
<string name="autofill_select_entry">Selectați intrarea…</string>
|
||||
|
||||
@@ -266,11 +266,11 @@
|
||||
<string name="education_donation_title">Участвуйте</string>
|
||||
<string name="education_donation_summary">Примите участие в проекте для повышения стабильности, безопасности и добавления новых возможностей.</string>
|
||||
<string name="html_text_ad_free">В отличие от многих других приложений управления паролями, здесь <strong>нет рекламы</strong>, и оно <strong>свободно от лицензирования</strong>. Приложение не собирает ваши личные данные на своих серверах независимо от версии, которую вы используете.</string>
|
||||
<string name="html_text_buy_pro">Купите про–версию и откройте доступ к этой <strong>теме</strong>. Покупая про-версию, вы помогаете <strong>разработчикам открытого ПО.</strong></string>
|
||||
<string name="html_text_buy_pro">Купите pro–версию и откройте доступ к этой <strong>теме</strong>. Покупая pro-версию, вы помогаете <strong>разработчикам открытого ПО.</strong></string>
|
||||
<string name="html_text_feature_generosity">Эти <strong>визуальные стили</strong> доступны благодаря вашей щедрости.</string>
|
||||
<string name="html_text_donation">Произведя <strong>вклад</strong> в проект <i>(в денежном выражении, программным кодом или переводом)</i>, вы поможете ему жить и процветать, а также получите право на разблокировку <strong>тем</strong>.</string>
|
||||
<string name="html_text_donation">Произведя <strong>вклад</strong> в проект <i>(в денежном выражении, программным кодом или переводом)</i>, вы поможете ему жить и процветать, а также получите право на разблокировку <strong>тем оформления</strong>.</string>
|
||||
<string name="html_text_dev_feature">Эта функция находится <strong>в разработке</strong> и требует вашего <strong>участия</strong>, чтобы стать доступной в ближайшее время.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Покупая <strong>про</strong>–версию,</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Покупая <strong>pro</strong>–версию,</string>
|
||||
<string name="html_text_dev_feature_contibute"><strong>Участвуя в проекте</strong>,</string>
|
||||
<string name="html_text_dev_feature_encourage">вы поощряете разработчиков добавлять <strong>новые возможности</strong> и <strong>исправлять ошибки</strong> в соответствии с вашими замечаниями.</string>
|
||||
<string name="html_text_dev_feature_thanks">Большое спасибо за поддержку.</string>
|
||||
@@ -498,7 +498,6 @@
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Распознавание разблокировки устройства</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">При использовании разблокировки устройства вам всё равно необходимо помнить основные учётные данные.</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Удалить все ключи шифрования, связанные с распознаванием разблокировки устройства\?</string>
|
||||
<string name="advanced_unlock_scanning_error">Ошибка разблокировки устройства: %1$s</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Настройка разблокировки устройства</string>
|
||||
<string name="menu_keystore_remove_key">Удалить ключ разблокировки устройства</string>
|
||||
<string name="enter">Ввод</string>
|
||||
@@ -652,7 +651,7 @@
|
||||
<string name="error_response_already_provided">Ответ уже предоставлен.</string>
|
||||
<string name="error_challenge_already_requested">Вызов уже запрошен.</string>
|
||||
<string name="error_no_response_from_challenge">Невозможно получить ответ на вызов.</string>
|
||||
<string name="error_unable_merge_database_kdb">Невозможно выполнить объединение с базой паролей в формате kdb</string>
|
||||
<string name="error_unable_merge_database_kdb">Невозможно выполнить объединение с базой паролей в формате kdb.</string>
|
||||
<string name="error_hardware_key_unsupported">Аппаратный ключ не поддерживается.</string>
|
||||
<string name="error_empty_key">Ключ не может быть пустым.</string>
|
||||
<string name="corrupted_file">Файл повреждён.</string>
|
||||
@@ -708,4 +707,4 @@
|
||||
<string name="warning_large_keyfile">Не рекомендуется добавлять большой ключевой файл, это может помешать открытию базы.</string>
|
||||
<string name="hide_templates_title">Скрывать шаблоны</string>
|
||||
<string name="hide_templates_summary">Не показывать шаблоны</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -476,7 +476,6 @@
|
||||
<string name="style_choose_title">Téma aplikácie</string>
|
||||
<string name="protection">Ochrana</string>
|
||||
<string name="configure_biometric">Nie sú zaregistrované žiadne biometrické údaje ani poverenia zariadenia.</string>
|
||||
<string name="advanced_unlock_scanning_error">Chyba odomykania zariadenia: %1$s</string>
|
||||
<string name="lock">Zamknúť</string>
|
||||
<string name="custom_fields">Vlastné polia</string>
|
||||
<string name="education_sort_summary">Vyberte spôsob triedenia záznamov a skupín.</string>
|
||||
@@ -700,4 +699,4 @@
|
||||
<string name="style_name_sun">Slnko</string>
|
||||
<string name="style_name_dark">Tmavý</string>
|
||||
<string name="nodes">Uzly</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -344,7 +344,6 @@
|
||||
<string name="show_entry_colors_title">Ngjyra zërash</string>
|
||||
<string name="hide_expired_entries_title">Fshihi zërat e skaduar</string>
|
||||
<string name="error_XML_malformed">XML e keqformuar.</string>
|
||||
<string name="advanced_unlock_scanning_error">Gabim shkyçjeje pajisjeje: %1$s</string>
|
||||
<string name="autofill_block">Blloko vetëplotësim</string>
|
||||
<string name="allow_no_password_summary">Lejon prekjen e butoni “Hape”, nëse s’janë përzgjedhur kredenciale</string>
|
||||
<string name="about_description">Sendërtim për Android i përgjegjësit KeePass të fjalëkalimeve</string>
|
||||
@@ -658,4 +657,4 @@
|
||||
<string name="education_advanced_unlock_summary">Lidheni fjalëkalimin tuaj me hollësi biometrike të skanuara, ose kredenciale pajisjeje, për të shkyçur shpejt bazën tuaj të dhënave.</string>
|
||||
<string name="warning_empty_password">Të vazhdohet pa mbrojtje shkyçjeje fjalëkalimesh?</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">S’arrihet të bëhet gati hapi i shkyçjes së pajisjes.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="save">சேமி</string>
|
||||
<string name="content_description_passphrase_word_count">கடவுச்சொல் சொல் எண்ணிக்கை</string>
|
||||
<string name="error_string_key">ஒவ்வொரு சரத்திற்கும் புலத்தின் பெயர் இருக்க வேண்டும்.</string>
|
||||
<string name="error_registration_read_only">புதிய உருப்படியைச் சேமிப்பது படிக்க மட்டுமே தரவுத்தளத்தில் அனுமதிக்கப்படாது</string>
|
||||
<string name="error_registration_read_only">புதிய உருப்படியைச் சேமிப்பது படிக்க மட்டுமே தரவுத்தளத்தில் அனுமதிக்கப்படாது.</string>
|
||||
<string name="field_name">புலம் பெயர்</string>
|
||||
<string name="menu_form_filling_settings">படிவம் நிரப்புதல்</string>
|
||||
<string name="menu_copy">நகலெடு</string>
|
||||
@@ -106,7 +106,7 @@
|
||||
<string name="education_field_copy_title">ஒரு புலத்தை நகலெடுக்கவும்</string>
|
||||
<string name="education_field_copy_summary">நகலெடுக்கப்பட்ட புலங்களை எங்கும் ஒட்டலாம்.\n\n நீங்கள் விரும்பும் படிவ நிரப்புதல் முறையைப் பயன்படுத்தவும்.</string>
|
||||
<string name="education_sort_summary">உள்ளீடுகள் மற்றும் குழுக்கள் எவ்வாறு வரிசைப்படுத்தப்படுகின்றன என்பதைத் தேர்வுசெய்க.</string>
|
||||
<string name="html_text_buy_pro">புரோ பதிப்பை வாங்குவதன் மூலம், இந்த <strong> விசுவல் பாணி </strong> க்கான அணுகலை நீங்கள் பெறுவீர்கள், மேலும் நீங்கள் குறிப்பாக <strong> சமூக திட்டங்களை உணர உதவுவீர்கள். </Strong></string>
|
||||
<string name="html_text_buy_pro">புரோ பதிப்பை வாங்குவதன் மூலம், இந்த <strong> விசுவல் பாணி </strong> க்கான அணுகலை நீங்கள் பெறுவீர்கள், மேலும் நீங்கள் குறிப்பாக <strong> சமூக திட்டங்களை உணர உதவுவீர்கள்.</strong></string>
|
||||
<string name="html_text_dev_feature_thanks">உங்கள் பங்களிப்புக்கு மிக்க நன்றி.</string>
|
||||
<string name="html_text_dev_feature_work_hard">இந்த அம்சத்தை விரைவாக வெளியிட நாங்கள் கடுமையாக உழைத்து வருகிறோம்.</string>
|
||||
<string name="html_text_dev_feature_upgrade">புதிய பதிப்புகளை நிறுவுவதன் மூலம் உங்கள் பயன்பாட்டை புதுப்பித்த நிலையில் வைத்திருக்க நினைவில் கொள்ளுங்கள்.</string>
|
||||
@@ -150,7 +150,7 @@
|
||||
<string name="version">பதிப்பு</string>
|
||||
<string name="entry_otp">OTP</string>
|
||||
<string name="entry_url">முகவரி</string>
|
||||
<string name="error_file_not_create">கோப்பை உருவாக்க முடியவில்லை</string>
|
||||
<string name="error_file_not_create">கோப்பை உருவாக்க முடியவில்லை.</string>
|
||||
<string name="error_load_database">தரவுத்தளத்தை ஏற்ற முடியவில்லை.</string>
|
||||
<string name="error_pass_gen_type">குறைந்தது ஒரு கடவுச்சொல் உருவாக்கும் வகை தேர்ந்தெடுக்கப்பட வேண்டும்.</string>
|
||||
<string name="error_label_exists">இந்த சிட்டை ஏற்கனவே உள்ளது.</string>
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="sort_groups_before">முன் குழுக்கள்</string>
|
||||
<string name="warning_no_encryption_key">குறியாக்க விசை இல்லாமல் தொடரவா?</string>
|
||||
<string name="warning_permanently_delete_nodes">தேர்ந்தெடுக்கப்பட்ட முனைகளை நிரந்தரமாக நீக்கவா?</string>
|
||||
<string name="advanced_unlock_scanning_error">சாதனம் திறத்தல் பிழை: %1$s</string>
|
||||
<string name="device_credential_unlock_enable_summary">தரவுத்தளத்தைத் திறக்க உங்கள் சாதன நற்சான்றிதழைப் பயன்படுத்தலாம்</string>
|
||||
<string name="biometric_delete_all_key_title">குறியாக்க விசைகளை நீக்கு</string>
|
||||
<string name="biometric_delete_all_key_summary">சாதன திறத்தல் ஏற்பு தொடர்பான அனைத்து குறியாக்க விசைகளையும் நீக்கு</string>
|
||||
@@ -192,7 +191,7 @@
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">சாதன திறத்தல் தரவுடன் தரவுத்தள நற்சான்றிதழைப் பிரித்தெடுக்கவும்</string>
|
||||
<string name="password_size_title">உருவாக்கப்பட்ட கடவுச்சொல் அளவு</string>
|
||||
<string name="assign_master_key">ஒரு முதன்மை விசையை ஒதுக்குங்கள்</string>
|
||||
<string name="error_import_app_properties">பயன்பாட்டு அமைப்புகள் இறக்குமதி செய்யும் போது பிழை</string>
|
||||
<string name="error_import_app_properties">பயன்பாட்டு அமைப்புகள் இறக்குமதி செய்யும்போது பிழை.</string>
|
||||
<string name="success_export_app_properties">பயன்பாட்டு அமைப்புகள் ஏற்றுமதி செய்யப்பட்டன</string>
|
||||
<string name="passphrase">கடவுச்சொல்</string>
|
||||
<string name="create_keepass_file">புதிய பெட்டகத்தை உருவாக்கவும்</string>
|
||||
@@ -221,7 +220,7 @@
|
||||
<string name="icon_pack_choose_summary">பயன்பாட்டில் பயன்படுத்தப்படும் படவுரு பேக்</string>
|
||||
<string name="hide_templates_title">வார்ப்புருக்கள் மறைக்க</string>
|
||||
<string name="hide_templates_summary">வார்ப்புருக்கள் காட்டப்படவில்லை</string>
|
||||
<string name="application">பயன்பாடு</string>
|
||||
<string name="application">செயலி</string>
|
||||
<string name="brackets">அடைப்புக்குறிப்புகள்</string>
|
||||
<string name="entry_accessed">அணுகப்பட்டது</string>
|
||||
<string name="entry_notes">குறிப்புகள்</string>
|
||||
@@ -269,7 +268,7 @@
|
||||
<string name="error_start_database_action">தரவுத்தளத்தில் ஒரு செயலைச் செய்யும்போது பிழை ஏற்பட்டது.</string>
|
||||
<string name="error_no_response_from_challenge">சவாலிலிருந்து பதிலைப் பெற முடியவில்லை.</string>
|
||||
<string name="error_driver_required">%1$s க்கான இயக்கி தேவை.</string>
|
||||
<string name="error_unable_merge_database_kdb">KDB தரவுத்தள கோப்புடன் ஒன்றிணைக்க முடியவில்லை</string>
|
||||
<string name="error_unable_merge_database_kdb">KDB தரவுத்தள கோப்புடன் ஒன்றிணைக்க முடியவில்லை.</string>
|
||||
<string name="error_location_unknown">தரவுத்தள இருப்பிடம் தெரியவில்லை, தரவுத்தள செயலைச் செய்ய முடியாது.</string>
|
||||
<string name="error_hardware_key_unsupported">வன்பொருள் விசை ஆதரிக்கப்படவில்லை.</string>
|
||||
<string name="error_empty_key">விசை காலியாக இருக்க முடியாது.</string>
|
||||
@@ -322,7 +321,7 @@
|
||||
<string name="export_app_properties_summary">பயன்பாட்டு அமைப்புகளை ஏற்றுமதி செய்ய ஒரு கோப்பை உருவாக்கவும்</string>
|
||||
<string name="description_app_properties">பயன்பாட்டு அமைப்புகளை நிர்வகிக்க KeepASSDX பண்புகள்</string>
|
||||
<string name="success_import_app_properties">பயன்பாட்டு அமைப்புகள் இறக்குமதி செய்யப்பட்டன</string>
|
||||
<string name="error_export_app_properties">பயன்பாட்டு அமைப்புகள் ஏற்றுமதியின் போது பிழை</string>
|
||||
<string name="error_export_app_properties">பயன்பாட்டு அமைப்புகள் ஏற்றுமதியின்போது பிழை.</string>
|
||||
<string name="encryption_explanation">எல்லா தரவிற்கும் பயன்படுத்தப்படும் தரவுத்தள குறியாக்க வழிமுறை</string>
|
||||
<string name="kdf_explanation">குறியாக்க வழிமுறைக்கான விசையை உருவாக்க, முதன்மை விசை தோராயமாக உப்பு விசை வழித்தோன்றல் செயல்பாட்டைப் பயன்படுத்தி மாற்றப்படுகிறது.</string>
|
||||
<string name="style_name_forest">காடு</string>
|
||||
@@ -337,7 +336,7 @@
|
||||
<string name="extended_ASCII">நீட்டிக்கப்பட்ட ASCII</string>
|
||||
<string name="allow">இசைவு</string>
|
||||
<string name="app_timeout_summary">தரவுத்தளத்தை பூட்டுவதற்கு முன் செயலற்ற நேரம்</string>
|
||||
<string name="app_timeout">நேரம் முடிந்தது</string>
|
||||
<string name="app_timeout">முடிவு நேரம்</string>
|
||||
<string name="key_derivation_function">முக்கிய வழித்தோன்றல் செயல்பாடு</string>
|
||||
<string name="validate">சரிபார்க்கவும்</string>
|
||||
<string name="entry_expires">காலாவதியாகிறது</string>
|
||||
@@ -385,7 +384,7 @@
|
||||
<string name="generate_keyfile">கீஃபைலை உருவாக்குங்கள்</string>
|
||||
<string name="nodes">முனைகள்</string>
|
||||
<string name="recursive_number_entries_title">உள்ளீடுகளின் சுழல்நிலை எண்ணிக்கை</string>
|
||||
<string name="recursive_number_entries_summary">ஒரு குழுவில் உள்ளீடுகளின் எண்ணிக்கையை மீண்டும் மீண்டும் கணக்கிடுகிறது</string>
|
||||
<string name="recursive_number_entries_summary">ஒரு குழுவில் உள்ளீடுகளின் எண்ணிக்கையை மறுநிகழ்வு கணக்கிடுகிறது</string>
|
||||
<string name="menu_app_settings_summary">தேடல், பூட்டு, வரலாறு, பண்புகள்</string>
|
||||
<string name="menu_form_filling_settings_summary">விசைப்பலகை, ஆட்டோஃபில், இடைநிலைப்பலகை</string>
|
||||
<string name="menu_empty_recycle_bin">மறுசுழற்சி தொட்டியை வெறுமை செய்யுங்கள்</string>
|
||||
@@ -398,8 +397,8 @@
|
||||
<string name="advanced_unlock_delete_all_key_warning">சாதன திறத்தல் ஏற்பு தொடர்பான அனைத்து குறியாக்க விசைகளையும் நீக்கவா?</string>
|
||||
<string name="recycle_bin_title">மறுசுழற்சி பின் பயன்பாடு</string>
|
||||
<string name="application_appearance">இடைமுகம்</string>
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
<string name="keyboard_name">Magikeyboard</string>
|
||||
<string name="magic_keyboard_title">மாயவிசைப்பலகை</string>
|
||||
<string name="keyboard_name">மாயவிசைப்பலகை</string>
|
||||
<string name="keyboard_label">Magikeyboard (keepassdx)</string>
|
||||
<string name="keyboard_selection_entry_title">நுழைவு தேர்வு</string>
|
||||
<string name="keyboard_notification_entry_title">அறிவிப்பு செய்தி</string>
|
||||
@@ -439,7 +438,7 @@
|
||||
<string name="entry_user_name">பயனர்பெயர்</string>
|
||||
<string name="error_arc4">ஆர்க்ஃபோர் ச்ட்ரீம் சைஃபர் ஆதரிக்கப்படவில்லை.</string>
|
||||
<string name="error_otp_type">தற்போதுள்ள OTP வகை இந்த படிவத்தால் அங்கீகரிக்கப்படவில்லை, அதன் சரிபார்ப்பு இனி கிள்ளாக்கை சரியாக உருவாக்காது.</string>
|
||||
<string name="error_challenge_already_requested">அறைகூவல் ஏற்கனவே கோரப்பட்டது</string>
|
||||
<string name="error_challenge_already_requested">அறைகூவல் ஏற்கனவே கோரப்பட்டது.</string>
|
||||
<string name="error_response_already_provided">பதில் ஏற்கனவே வழங்கப்பட்டுள்ளது.</string>
|
||||
<string name="error_cancel_by_user">பயனரால் ரத்து செய்யப்பட்டது.</string>
|
||||
<string name="list_entries_show_username_title">பயனர்பெயர்களைக் காட்டு</string>
|
||||
@@ -662,8 +661,8 @@
|
||||
<string name="education_donation_summary">ச்திரத்தன்மை, பாதுகாப்பு மற்றும் கூடுதல் அம்சங்களைச் சேர்ப்பதில் அதிகரிக்க உதவுங்கள்.</string>
|
||||
<string name="html_text_ad_free">பல கடவுச்சொல் மேலாண்மை பயன்பாடுகளைப் போலன்றி, இது <strong> விளம்பர-இலவச </strong>, <strong> நகலெடுக்கப்பட்ட லிப்ரே மென்பொருள் </strong> மற்றும் நீங்கள் எந்த பதிப்பைப் பயன்படுத்தினாலும் அதன் சேவையகங்களில் தனிப்பட்ட தரவை சேகரிக்காது.</string>
|
||||
<string name="html_text_feature_generosity">இந்த <strong> விசுவல் பாணி </strong> உங்கள் தாராள மனப்பான்மைக்கு நன்றி.</string>
|
||||
<string name="html_text_donation">திட்டத்திற்கு <i> (பண ரீதியாக, குறியீடு, மொழிபெயர்ப்பு) </i> க்கு <strong> பங்களிப்பு </strong> மூலம், நீங்கள் தொடர்ந்து வாழவும் வளரவும் உதவுவீர்கள், மேலும் நீங்கள் <strong> கருப்பொருளுக்கும் தகுதி பெறுவீர்கள் </strong> திறத்தல் செயல்முறை.</string>
|
||||
<string name="html_text_donation">திட்டத்திற்கு <strong>பங்களிப்பு</strong> மூலம் <i>(பண ரீதியாக, குறியீடு, மொழிபெயர்ப்பு)</i>, நீங்கள் தொடர்ந்து வாழவும் வளரவும் உதவுவீர்கள், மேலும் நீங்கள் <strong>கருப்பொருளுக்கும்</strong> தகுதி பெறுவீர்கள்திறத்தல் செயல்முறை.</string>
|
||||
<string name="upload_attachment">%1$s ஐ பதிவேற்றவும்</string>
|
||||
<string name="download_initialization">தொடங்குதல்…</string>
|
||||
<string name="download_progression">செயலில்:%1$d %%</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -525,7 +525,6 @@
|
||||
<string name="advanced_unlock_invalid_key">อ่านกุญแจการปลดล็อกของอุปกรณ์ไม่ได้ โปรดลบข้อมูลออกและเพื่มข้อมูลการปลดล็อกด้วยอุปกรณ์อีกครั้ง</string>
|
||||
<string name="advanced_unlock_not_recognized">ไม่รู้จักลายนิ้วมือ</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">แยกข้อมูลประจำตัวออกด้วยข้อมูลการปลดล็อกด้วยอุปกรณ์</string>
|
||||
<string name="advanced_unlock_scanning_error">การปลดล็อกด้วยอุปกรณ์ผิดพลาด: %1$s</string>
|
||||
<string name="properties">คุณสมบัติ</string>
|
||||
<string name="unavailable">ฐานข้อมูลนี้ยังไม่มีข้อมูลการเข้าสูระบบเลย</string>
|
||||
<string name="database_history">ประวัติ</string>
|
||||
@@ -680,4 +679,4 @@
|
||||
<string name="warning_large_keyfile">ไม่แนะนำให้ใช้ keyfile ขนาดใหญ่ๆ เพราะอาจจะทำให้เปิดฐานข้อมูลรหัสผ่านไม่ได้</string>
|
||||
<string name="hide_templates_title">ซ่อนแม่แบบ</string>
|
||||
<string name="hide_templates_summary">แม่แบบไม่แสดง</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -488,7 +488,6 @@
|
||||
<string name="credential_before_click_advanced_unlock_button">Parolayı yazın ve ardından bu düğmeye tıklayın.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Cihaz kilit açma istemi başlatılamıyor.</string>
|
||||
<string name="unavailable">Kullanım dışı</string>
|
||||
<string name="advanced_unlock_scanning_error">Cihaz kilit açma hatası: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Cihaz kilit açma parmak izi tanınamadı</string>
|
||||
<string name="advanced_unlock_invalid_key">Cihazın kilit açma anahtarı okunamıyor. Lütfen silin ve kilit açma tanıma prosedürünü tekrarlayın.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Cihaz kilit açma verileriyle veritabanı kimlik bilgilerini çıkarın</string>
|
||||
|
||||
@@ -493,7 +493,6 @@
|
||||
<string name="device_credential">Облікові дані пристрою</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Введіть пароль, а потім натисніть цю кнопку.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Не вдалося ініціалізувати запит на розблокування пристрою.</string>
|
||||
<string name="advanced_unlock_scanning_error">Помилка розблокування пристрою: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Не вдалося розпізнати розблокування пристрою</string>
|
||||
<string name="advanced_unlock_invalid_key">Не вдалося розпізнати ключ розблокування пристрою. Видаліть його й повторіть процедуру створення ключа.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Витягування облікових даних бази даних за допомогою даних розблокування пристрою</string>
|
||||
|
||||
@@ -400,7 +400,6 @@
|
||||
<string name="encrypted_value_stored">Đã lưu trữ mật khẩu được mã hóa</string>
|
||||
<string name="advanced_unlock_invalid_key">Không thể đọc được mã mở khóa thiết bị. Vui lòng xóa nó và lặp lại quy trình nhận dạng mở khóa.</string>
|
||||
<string name="advanced_unlock_not_recognized">Không thể nhận dạng vân tay mở khóa thiết bị</string>
|
||||
<string name="advanced_unlock_scanning_error">Lỗi mở khóa thiết bị: %1$s</string>
|
||||
<string name="unavailable">Không có sẵn</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Không thể khởi tạo lời nhắc mở khóa thiết bị.</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Nhập mật khẩu rồi nhấp vào nút này.</string>
|
||||
@@ -731,4 +730,4 @@
|
||||
<string name="hide_expired_entries_title">Ẩn các mục đã hết hạn</string>
|
||||
<string name="hide_expired_entries_summary">Các mục hết hạn không được hiển thị</string>
|
||||
<string name="warning_database_info_changed_options_read_only">Tải lại cơ sở dữ liệu với những thay đổi mới nhất.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -294,7 +294,7 @@
|
||||
<string name="html_text_ad_free">不同于大多数的密码管理应用,无论您是使用免费版本还是付费版本的 KeePassDX,这都是一款<strong>没有广告</strong>,<strong>基于 copylefted 版权协议的自由软件</strong>。同时,本软件的任何版本都不会收集您的任何个人信息。</string>
|
||||
<string name="html_text_buy_pro">通过购买高级版本,您将解锁全部<strong>主题样式</strong>,重要的是,您会为<strong>社区项目的进行</strong>提供帮助</string>
|
||||
<string name="html_text_feature_generosity">此<strong>主题样式</strong>已可用,感谢您的慷慨相助。</string>
|
||||
<string name="html_text_donation">通过多方面向本项目<strong>作贡献</strong>,可以是<i>(财政、代码、翻译等)</i>,你将帮助本项目继续走下去并茁壮成长,并获得<strong>主题</strong>解锁步骤资格。</string>
|
||||
<string name="html_text_donation">通过多方面向本项目<strong>作贡献</strong>,可以是<i>(金钱、代码、翻译等)</i>,你将帮助本项目继续走下去并茁壮成长,并获得<strong>主题</strong>解锁步骤资格。</string>
|
||||
<string name="html_text_dev_feature">此功能目前<strong>仍在开发中</strong>,<strong>捐助</strong>将使该功能很快变得可用。</string>
|
||||
<string name="html_text_dev_feature_buy_pro">通过购买<strong>专业</strong>版,</string>
|
||||
<string name="html_text_dev_feature_contibute">通过<strong>贡献</strong>,</string>
|
||||
@@ -492,7 +492,6 @@
|
||||
<string name="device_credential">设备凭据</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">输入密码,然后点击这个按钮。</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">无法初始化设备解锁提示。</string>
|
||||
<string name="advanced_unlock_scanning_error">设备解锁出错:%1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">无法识别设备解锁印记</string>
|
||||
<string name="advanced_unlock_invalid_key">无法读取设备解锁密钥。请删除它,并重复解锁识别步骤。</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">用设备解锁数据提取数据库凭据</string>
|
||||
@@ -707,4 +706,4 @@
|
||||
<string name="hide_templates_title">隐藏模板</string>
|
||||
<string name="nodes">节点</string>
|
||||
<string name="recursive_number_entries_title">条目的递归数</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
<string name="advanced_unlock_prompt_not_initialized">無法初始化裝置解鎖提示。</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">即使你使用裝置解鎖識別,你仍然需要記住你的解鎖憑證。</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">裝置解鎖連線</string>
|
||||
<string name="advanced_unlock_scanning_error">裝置解鎖出錯:%1$s</string>
|
||||
<string name="advanced_unlock_tap_delete">點擊刪除裝置解鎖密鑰</string>
|
||||
<string name="advanced_unlock_timeout">裝置解鎖超時</string>
|
||||
<string name="allow">允許</string>
|
||||
@@ -700,4 +699,4 @@
|
||||
<string name="nodes">節點</string>
|
||||
<string name="recursive_number_entries_summary">遞歸計算群組中的項目數</string>
|
||||
<string name="warning_large_keyfile">不建議增加較大的密鑰檔案,這可能會阻止資料庫開啟。</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -407,7 +407,6 @@
|
||||
<string name="encrypted_value_stored">Encrypted password stored</string>
|
||||
<string name="advanced_unlock_invalid_key">Cannot read the device unlock key. Please delete it and repeat the unlock recognition procedure.</string>
|
||||
<string name="advanced_unlock_not_recognized">Could not recognize device unlock print</string>
|
||||
<string name="advanced_unlock_scanning_error">Device unlock error: %1$s</string>
|
||||
<string name="unavailable">Unavailable</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Unable to initialize device unlock prompt.</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Type in the password, and then click this button.</string>
|
||||
|
||||
@@ -10,7 +10,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.11.0'
|
||||
classpath 'com.android.tools.build:gradle:8.11.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,5 +42,6 @@ dependencies {
|
||||
// Crypto
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||
|
||||
androidTestImplementation "androidx.test:runner:$android_test_version"
|
||||
testImplementation "androidx.test:runner:$android_test_version"
|
||||
}
|
||||
|
||||
@@ -892,6 +892,7 @@ open class Database {
|
||||
return mSearchHelper.createVirtualGroupWithSearchResult(this,
|
||||
SearchParameters().apply {
|
||||
searchQuery = searchInfoString
|
||||
allowEmptyQuery = false
|
||||
searchInTitles = true
|
||||
searchInUsernames = false
|
||||
searchInPasswords = false
|
||||
|
||||
@@ -24,52 +24,115 @@ import java.util.*
|
||||
|
||||
class TemplateBuilder {
|
||||
|
||||
private val urlAttribute = TemplateAttribute(TemplateField.LABEL_URL, TemplateAttributeType.TEXT)
|
||||
private val usernameAttribute = TemplateAttribute(TemplateField.LABEL_USERNAME, TemplateAttributeType.TEXT)
|
||||
private val urlAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_URL,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val usernameAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_USERNAME,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val notesAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_NOTES,
|
||||
TemplateAttributeType.TEXT,
|
||||
false,
|
||||
TemplateAttributeOption().apply {
|
||||
label = TemplateField.LABEL_NOTES,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply {
|
||||
setNumberLinesToMany()
|
||||
})
|
||||
private val holderAttribute = TemplateAttribute(TemplateField.LABEL_HOLDER, TemplateAttributeType.TEXT)
|
||||
private val numberAttribute = TemplateAttribute(TemplateField.LABEL_NUMBER, TemplateAttributeType.TEXT)
|
||||
private val cvvAttribute = TemplateAttribute(TemplateField.LABEL_CVV, TemplateAttributeType.TEXT, true)
|
||||
private val pinAttribute = TemplateAttribute(TemplateField.LABEL_PIN, TemplateAttributeType.TEXT, true)
|
||||
private val nameAttribute = TemplateAttribute(TemplateField.LABEL_NAME, TemplateAttributeType.TEXT)
|
||||
private val placeOfIssueAttribute = TemplateAttribute(TemplateField.LABEL_PLACE_OF_ISSUE, TemplateAttributeType.TEXT)
|
||||
}
|
||||
)
|
||||
private val holderAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_HOLDER,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val numberAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_NUMBER,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val cvvAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_CVV,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val pinAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PIN,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val nameAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_NAME,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val placeOfIssueAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PLACE_OF_ISSUE,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val dateOfIssueAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_DATE_OF_ISSUE,
|
||||
TemplateAttributeType.DATETIME,
|
||||
false,
|
||||
TemplateAttributeOption().apply {
|
||||
label = TemplateField.LABEL_DATE_OF_ISSUE,
|
||||
type = TemplateAttributeType.DATETIME,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply {
|
||||
setDateFormatToDate()
|
||||
})
|
||||
}
|
||||
)
|
||||
private val expirationDateAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_EXPIRATION,
|
||||
TemplateAttributeType.DATETIME,
|
||||
false,
|
||||
TemplateAttributeOption().apply {
|
||||
label = TemplateField.LABEL_EXPIRATION,
|
||||
type = TemplateAttributeType.DATETIME,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply {
|
||||
setDateFormatToDate()
|
||||
})
|
||||
private val emailAddressAttribute = TemplateAttribute(TemplateField.LABEL_EMAIL_ADDRESS, TemplateAttributeType.TEXT)
|
||||
private val passwordAttribute = TemplateAttribute(TemplateField.LABEL_PASSWORD, TemplateAttributeType.TEXT, true)
|
||||
private val ssidAttribute = TemplateAttribute(TemplateField.LABEL_SSID, TemplateAttributeType.TEXT)
|
||||
}
|
||||
)
|
||||
private val emailAddressAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_USERNAME,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
options = TemplateAttributeOption().apply {
|
||||
alias = TemplateField.LABEL_EMAIL_ADDRESS
|
||||
}
|
||||
)
|
||||
private val passwordAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PASSWORD,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val ssidAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_SSID,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val wirelessTypeAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_TYPE,
|
||||
TemplateAttributeType.LIST,
|
||||
false,
|
||||
TemplateAttributeOption().apply{
|
||||
label = TemplateField.LABEL_TYPE,
|
||||
type = TemplateAttributeType.LIST,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply{
|
||||
setListItems("WPA3", "WPA2", "WPA", "WEP")
|
||||
default = "WPA2"
|
||||
})
|
||||
private val tokenAttribute = TemplateAttribute(TemplateField.LABEL_TOKEN, TemplateAttributeType.TEXT)
|
||||
private val publicKeyAttribute = TemplateAttribute(TemplateField.LABEL_PUBLIC_KEY, TemplateAttributeType.TEXT)
|
||||
private val privateKeyAttribute = TemplateAttribute(TemplateField.LABEL_PRIVATE_KEY, TemplateAttributeType.TEXT, true)
|
||||
private val seedAttribute = TemplateAttribute(TemplateField.LABEL_SEED, TemplateAttributeType.TEXT, true)
|
||||
private val bicAttribute = TemplateAttribute(TemplateField.LABEL_BIC, TemplateAttributeType.TEXT)
|
||||
private val ibanAttribute = TemplateAttribute(TemplateField.LABEL_IBAN, TemplateAttributeType.TEXT)
|
||||
}
|
||||
)
|
||||
private val tokenAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_TOKEN,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val publicKeyAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PUBLIC_KEY,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val privateKeyAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PRIVATE_KEY,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val seedAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_SEED,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val bicAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_BIC,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val ibanAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_IBAN,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
|
||||
val email: Template
|
||||
get() {
|
||||
|
||||
@@ -123,11 +123,9 @@ class SearchHelper {
|
||||
*/
|
||||
fun searchInEntry(entry: Entry,
|
||||
searchParameters: SearchParameters): Boolean {
|
||||
val searchQuery = searchParameters.searchQuery
|
||||
|
||||
// Not found if the search string is empty
|
||||
if (searchQuery.isEmpty())
|
||||
return false
|
||||
if (searchParameters.searchQuery.isEmpty())
|
||||
return searchParameters.allowEmptyQuery
|
||||
|
||||
// Exclude entry expired
|
||||
if (!searchParameters.searchInExpired) {
|
||||
@@ -205,10 +203,11 @@ class SearchHelper {
|
||||
}
|
||||
regex.matches(stringToCheck)
|
||||
} else {
|
||||
searchParameters.searchQuery.split(" ").any { word ->
|
||||
specialComparison?.invoke(stringToCheck, word)
|
||||
?: stringToCheck.contains(word, !searchParameters.caseSensitive)
|
||||
}
|
||||
specialComparison?.invoke(stringToCheck, searchParameters.searchQuery)
|
||||
?: stringToCheck.contains(
|
||||
searchParameters.searchQuery,
|
||||
!searchParameters.caseSensitive
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.Parcelable
|
||||
*/
|
||||
class SearchParameters() : Parcelable{
|
||||
var searchQuery: String = ""
|
||||
var allowEmptyQuery = true
|
||||
var caseSensitive = false
|
||||
var isRegex = false
|
||||
|
||||
@@ -49,6 +50,7 @@ class SearchParameters() : Parcelable{
|
||||
|
||||
constructor(parcel: Parcel) : this() {
|
||||
searchQuery = parcel.readString() ?: searchQuery
|
||||
allowEmptyQuery = parcel.readByte() != 0.toByte()
|
||||
caseSensitive = parcel.readByte() != 0.toByte()
|
||||
isRegex = parcel.readByte() != 0.toByte()
|
||||
searchInTitles = parcel.readByte() != 0.toByte()
|
||||
@@ -69,6 +71,7 @@ class SearchParameters() : Parcelable{
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(searchQuery)
|
||||
parcel.writeByte(if (allowEmptyQuery) 1 else 0)
|
||||
parcel.writeByte(if (caseSensitive) 1 else 0)
|
||||
parcel.writeByte(if (isRegex) 1 else 0)
|
||||
parcel.writeByte(if (searchInTitles) 1 else 0)
|
||||
|
||||
23
fastlane/metadata/android/ar/full_description.txt
Normal file
23
fastlane/metadata/android/ar/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
تطبيق KeePassDX هو <b>خزنة ومدير كلمات سر</b> يسمح بتحرير <b>البيانات المُعمَّاة في ملف واحد</b> بتنسيق KeePass المفتوح و<b>ملء النماذج بطريقة آمنة</b>، ولا يتطلب <b>اتصالاً بالإنترنت</b> ويتكامل مع معايير تصميم أندرويد. التطبيق <b>مفتوح المصدر، وبدون إعلانات</b>.
|
||||
|
||||
<b>الميزات</b>
|
||||
|
||||
- إنشاء ملفات/إدخالات وقواعد بيانات ومجموعات.
|
||||
- دعم ملفات .kdb و .kdbx (الإصدار 1 إلى 4) باستخدام خوارزميات AES - Twofish - ChaCha20 - Argon2.
|
||||
- متوافق مع غالبية البرامج البديلة (KeePass، KeePassXC، KeeWeb، ...).
|
||||
- يسمح بفتح ونسخ حقول URI / URL بسرعة.
|
||||
- التعرف البيومتري لفتح القفل بسرعة (بصمة الإصبع / فتح الوجه / ...).
|
||||
- إدارة كلمات السر لمرة واحدة (HOTP / TOTP) للمصادقة الثنائية (2FA).
|
||||
- تصميم Material مع سمات.
|
||||
- التعبئة التلقائية والتكامل.
|
||||
- لوحة مفاتيح لملء الحقول.
|
||||
- قوالب ديناميكية.
|
||||
- سجل لكل إدخال.
|
||||
- إدارة دقيقة للإعدادات.
|
||||
- الرمز مكتوب بلغات أصيلة (Kotlin / Java / JNI / C).
|
||||
|
||||
يمكنك التبرع أو شراء الإصدار الاحترافي للحصول على خدمة أفضل وتطوير سريع للميزات التي تريدها: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
المشروع يتطور باستمرار. لا تتردد في التحقق من حالة تطوير التحديثات القادمة: <a href="https://github.com/Kunzisoft/KeePassDX/projects">https://github.com/Kunzisoft/KeePassDX/projects</a>
|
||||
|
||||
أرسل العلل إلى: <a href="https://github.com/Kunzisoft/KeePassDX/issues">https://github.com/Kunzisoft/KeePassDX/issues</a>
|
||||
1
fastlane/metadata/android/ar/short_description.txt
Normal file
1
fastlane/metadata/android/ar/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
مدير وحافظة كلمات سر آمنة ومفتوحة المصدر
|
||||
1
fastlane/metadata/android/ar/title.txt
Normal file
1
fastlane/metadata/android/ar/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
KeePassDX - مدير كلمات سر مفتوح المصدر
|
||||
23
fastlane/metadata/android/cs-CZ/full_description.txt
Normal file
23
fastlane/metadata/android/cs-CZ/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
KeePassDX je <b>trezor a správce hesel</b>, který umožňuje upravovat <b>šifrovaná data v jednom souboru</b> v otevřeném formátu KeePass a <b>bezpečně vyplňovat formuláře</b>, <b>nevyžaduje připojení k internetu</b> a integruje standardy designu systému Android. Aplikace je <b>open source a bez reklam</b>.
|
||||
|
||||
<b>Funkce</b>
|
||||
|
||||
- Vytváření databázových souborů / záznamů a skupin.
|
||||
- Podpora souborů .kdb a .kdbx (verze 1 až 4) s algoritmy AES - Twofish - ChaCha20 - Argon2.
|
||||
- Kompatibilní s většinou alternativních programů (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Umožňuje rychlé otevírání a kopírování polí URI / URL.
|
||||
- Biometrické rozpoznávání pro rychlé odemykání (otisk prstu / rozpoznání obličeje / …).
|
||||
- Správa jednorázových hesel (HOTP / TOTP) pro dvoufaktorové ověřování (2FA).
|
||||
- Material Design s motivy.
|
||||
- Automatické vyplňování a integrace.
|
||||
- Klávesnice pro vyplňování polí.
|
||||
- Dynamické šablony.
|
||||
- Historie každého záznamu.
|
||||
- Přesná správa nastavení.
|
||||
- Kód napsaný v nativních jazycích (Kotlin / Java / JNI / C).
|
||||
|
||||
Můžete přispět nebo si koupit profesionální verzi pro lepší služby a rychlejší vývoj funkcí, které si přejete: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
Projekt se neustále vyvíjí. Neváhejte zkontrolovat stav vývoje dalších aktualizací: <a href="https://github.com/Kunzisoft/KeePassDX/projects">https://github.com/Kunzisoft/KeePassDX/projects</a>
|
||||
|
||||
Problémy hlaste na: <a href="https://github.com/Kunzisoft/KeePassDX/issues">https://github.com/Kunzisoft/KeePassDX/issues</a>
|
||||
1
fastlane/metadata/android/cs-CZ/short_description.txt
Normal file
1
fastlane/metadata/android/cs-CZ/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Bezpečný open-source trezor a správce hesel
|
||||
1
fastlane/metadata/android/cs-CZ/title.txt
Normal file
1
fastlane/metadata/android/cs-CZ/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
KeePassDX – FOSS trezor hesel
|
||||
@@ -1,19 +1,20 @@
|
||||
KeePassDX ist ein <b>Passwort-Safe und -Manager</b>, der es ermöglicht, verschlüsselte <b>Daten in einer einzigen Datei</b> im offenen KeePass-Format zu bearbeiten und <b>die Formulare auf sichere Weise auszufüllen</b>, <b>erfordert keine Internetverbindung</b> und integriert Android-Designstandards. Die App ist <b>Open Source, ohne Werbung</b>.
|
||||
|
||||
<b>Funktionalitäten</b>
|
||||
- Erstellung von Datenbanken / Einträgen und Gruppen.
|
||||
- Unterstützung von .kdb- und .kdbx-Dateien (Version 1 bis 4) mit AES - Twofish - ChaCha20 - Argon2 Algorithmus.
|
||||
- Kompatibel mit den meisten alternativen Programmen (KeePass, KeePassXC, KeeWeb, ...).
|
||||
- Ermöglicht das schnelle Kopieren von Feldern und das Öffnen von URIs /URLs.
|
||||
- Biometrische Erkennung für eine schnelle Entsperrung (Fingerabdrücke / Entsperrung durch Gesicht / ...).
|
||||
- Verwaltung von Einmalpasswörtern (One-Time Password HOTP / TOTP) für die Zwei-Faktor-Authentifizierung (2FA).
|
||||
- Material Design mit Themen.
|
||||
- Automatisches Ausfüllen von Feldern.
|
||||
- Tastatur zum Ausfüllen von Feldern.
|
||||
- Dynamische Schablonen.
|
||||
- Historie jedes Eintrags.
|
||||
- Präzise Verwaltung der Parameter.
|
||||
- In nativen Sprachen geschriebener Code (Kotlin / Java / JNI / C).
|
||||
|
||||
- Erstellung von Datenbanken / Einträgen und Gruppen.
|
||||
- Unterstützung von .kdb- und .kdbx-Dateien (Version 1 bis 4) mit AES - Twofish - ChaCha20 - Argon2 Algorithmus.
|
||||
- Kompatibel mit den meisten alternativen Programmen (KeePass, KeePassXC, KeeWeb, ...).
|
||||
- Ermöglicht das schnelle Kopieren von Feldern und das Öffnen von URIs /URLs.
|
||||
- Biometrische Erkennung für eine schnelle Entsperrung (Fingerabdrücke / Entsperrung durch Gesicht / ...).
|
||||
- Verwaltung von Einmalpasswörtern (One-Time Password HOTP / TOTP) für die Zwei-Faktor-Authentifizierung (2FA).
|
||||
- Material Design mit Themen.
|
||||
- Automatisches Ausfüllen von Feldern.
|
||||
- Tastatur zum Ausfüllen von Feldern.
|
||||
- Dynamische Schablonen.
|
||||
- Historie jedes Eintrags.
|
||||
- Präzise Verwaltung der Parameter.
|
||||
- In nativen Sprachen geschriebener Code (Kotlin / Java / JNI / C).
|
||||
|
||||
Sie können spenden oder die Pro-Version kaufen, um einen besseren Service und eine schnelle Entwicklung der von Ihnen gewünschten Funktionen zu erhalten: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
Sicherer Open-Source-Passwort-Safe und -Manager
|
||||
Sicherer Open-Source-Passwort-Tresor und -Manager
|
||||
1
fastlane/metadata/android/de-DE/title.txt
Normal file
1
fastlane/metadata/android/de-DE/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
KeePassDX – FOSS-Passworttresor
|
||||
@@ -3,6 +3,6 @@
|
||||
* Fix Group notes #2053
|
||||
* Fix Dialog background #2005 #2004 (Thx @codokie)
|
||||
* Fix OTP configuration #2042 #2065 (Thx @Dev-ClayP)
|
||||
* Fix small UI elements #1987 #2007 (Thx @ymcx)
|
||||
* Fix small UI elements #1987 #2007 (Thx @ymcx)
|
||||
* RTL layout support #2021 (Thx @codokie)
|
||||
* App Metadata to translation #1823
|
||||
5
fastlane/metadata/android/en-US/changelogs/135.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/135.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
* Fix Autofill Registration #2089
|
||||
* Fix Biometric errors #2081
|
||||
* Fixed timestamp in copy file #1981 #1983
|
||||
* Fix Template Email #1986
|
||||
* Fix Search #2096
|
||||
3
fastlane/metadata/android/en-US/changelogs/136.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/136.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
* Fix UnlockManager #2098 #2101
|
||||
* Auto device unlock prompt #2105
|
||||
* Small fixes ##2066
|
||||
@@ -1,19 +1,20 @@
|
||||
KeePassDX is a <b>password safe and manager</b> allows editing <b>encrypted data in a single file</b> in the open KeePass format and <b>fill in the forms in a secure way</b>, requires <b>no Internet connection</b> and integrates Android design standards. The app is <b>open source, with no advertising</b>.
|
||||
|
||||
<b>Features</b>
|
||||
- Create database files / entries and groups.
|
||||
- Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
|
||||
- Compatible with the majority of alternative programs (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Allows opening and copying URI / URL fields quickly.
|
||||
- Biometric recognition for fast unlocking (fingerprint / face unlock / …).
|
||||
- One-time password management (HOTP / TOTP) for two-factor authentication (2FA).
|
||||
- Material design with themes.
|
||||
- Auto-Fill and integration.
|
||||
- Field filling keyboard.
|
||||
- Dynamic templates.
|
||||
- History of each entry.
|
||||
- Precise management of settings.
|
||||
- Code written in native languages (Kotlin / Java / JNI / C).
|
||||
|
||||
- Create database files / entries and groups.
|
||||
- Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
|
||||
- Compatible with the majority of alternative programs (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Allows opening and copying URI / URL fields quickly.
|
||||
- Biometric recognition for fast unlocking (fingerprint / face unlock / …).
|
||||
- One-time password management (HOTP / TOTP) for two-factor authentication (2FA).
|
||||
- Material design with themes.
|
||||
- Auto-Fill and integration.
|
||||
- Field filling keyboard.
|
||||
- Dynamic templates.
|
||||
- History of each entry.
|
||||
- Precise management of settings.
|
||||
- Code written in native languages (Kotlin / Java / JNI / C).
|
||||
|
||||
You can donate or buy the pro version for better service and a quick development of features you want: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
23
fastlane/metadata/android/es-ES/full_description.txt
Normal file
23
fastlane/metadata/android/es-ES/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
KeePassDX es un <b> Administrador de contraseñas seguro </b> que permite editar <b>datos cifrados en un solo archivo</b> en el formato abierto de KeePass permite <b>completar los formularios de manera segura</b>, no requiere<b>conexión a internet</b> e integra los estándares de diseño de Android. La aplicación es <b> de código abierto, sin publicidad</b>.
|
||||
|
||||
<b>Funciones</b>
|
||||
|
||||
- Crea archivos de bases de datos / entradas y grupos.
|
||||
- Compatible con archivos .kdb y .kdbx (versiones 1 a 4) con AES - Twofish - ChaCha20 y el algoritmo Argon2 ..
|
||||
- Compatible con la mayoría de programas alternativos (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Permite abrir y copiar campos URI / URL rapidamente.
|
||||
- Reconocimiento biometrico para desbloqueo rapido(huella digital/ reconocimiento facial/ …).
|
||||
- Gestor de claves de un solo uso(HOTP / TOTP) autentificación de dos factores(2FA).
|
||||
- Diseño Material con temas.
|
||||
- Autocompletado (Auto-Fill) e integración.
|
||||
- Teclado para llenado de campos.
|
||||
- Plantillas dinámicas.
|
||||
- Historial de cada entrada.
|
||||
- Gestión precisa de configuraciones.
|
||||
- Código escrito en lenguajes nativos (Kotlin / Java / JNI / C).
|
||||
|
||||
> Puedes donar o comprar la versión Pro para obtener un mejor servicio y un desarrollo más rápido de las funciones que deseas:<a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
El proyecto está en constante evolución. No dudes en consultar el estado de desarrollo de las próximas actualizaciones: <a href="https://github.com/Kunzisoft/KeePassDX/projects">https://github.com/Kunzisoft/KeePassDX/projects</a>
|
||||
|
||||
Reporta problemas: <a href="https://github.com/Kunzisoft/KeePassDX/issues">https://github.com/Kunzisoft/KeePassDX/issues</a>
|
||||
1
fastlane/metadata/android/es-ES/short_description.txt
Normal file
1
fastlane/metadata/android/es-ES/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Administrador de contraseñas seguro de código abierto
|
||||
1
fastlane/metadata/android/es-ES/title.txt
Normal file
1
fastlane/metadata/android/es-ES/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
KeePassDX - FOSS Administrador de contraseñas
|
||||
23
fastlane/metadata/android/et/full_description.txt
Normal file
23
fastlane/metadata/android/et/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
KeePassDX on <b>salasõnalaegas ja -haldur</b>, mis võimaldab <b>muuta krüptitud andmeid avatud KeePass vormingus ühes failis</b> ja <b>täita vorme turvaliselt</b>, ei vaja <b>internetiühendust</b> ning järgib Androidi kujundus-standardeid. Rakendus on <b>avatud lähtekoodiga ja reklaamivaba</b>.
|
||||
|
||||
<b>Funktsionaaldus</b>
|
||||
|
||||
- Andmebaasi failide, kirjete ja gruppide loomine.
|
||||
- .kdb ja .kdbx failide tugi (versioonid 1 kuni 4) AES - Twofish - ChaCha20 - Argon2 algoritmidega.
|
||||
- Ühildub enamiku alternatiivsete prakendustega (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Kiire võrguaadresside (URI / URL) väljade avamine ja kopeerimine.
|
||||
- Biomeetriline autentimine kiireks lukustuse eemaldamiseks (sõrmejälg / näotuvastus / …).
|
||||
- Ühekordsete slasõnade haldus (HOTP / TOTP) kahefaktorilise autentimise (2FA) jaoks.
|
||||
- MD kujunduskeel koos kujundustega.
|
||||
- Automaattäide ja lõiming.
|
||||
- Klahvistik väljade täitmiseks.
|
||||
- Dünaamilised mallid.
|
||||
- Iga kirje ajalugu.
|
||||
- Täpne seadistuste haldus.
|
||||
- Lähtekood on kirjutatud platvormiomases keeltes (Kotlin / Java / JNI / C).
|
||||
|
||||
Parema teenuse ja kiirema arendustöö huvides saad sa annetada või osta pro versiooni: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
Projekt areneb pidevalt. Ära kõhkle kontrollimast järgmiste arenduste olekut: <a href="https://github.com/Kunzisoft/KeePassDX/projects">https://github.com/Kunzisoft/KeePassDX/projects</a>
|
||||
|
||||
Veateated lisa palun siia: <a href="https://github.com/Kunzisoft/KeePassDX/issues">https://github.com/Kunzisoft/KeePassDX/issues</a>
|
||||
1
fastlane/metadata/android/et/short_description.txt
Normal file
1
fastlane/metadata/android/et/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Turvaline avatud lähtekoodiga salasõnalaegas ja -haldur
|
||||
1
fastlane/metadata/android/et/title.txt
Normal file
1
fastlane/metadata/android/et/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
KeePassDX: sinu salasõnalaegas
|
||||
5
fastlane/metadata/android/fr-FR/changelogs/135.txt
Normal file
5
fastlane/metadata/android/fr-FR/changelogs/135.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
* Correction de l'enregistrement pour le remplissage automatique #2089
|
||||
* Correction des erreurs biométriques #2081
|
||||
* Correction du timestamp dans le fichier de copie #1981 #1983
|
||||
* Correction des gabaris Email #1986
|
||||
* Correction de la recherche #2096
|
||||
3
fastlane/metadata/android/fr-FR/changelogs/136.txt
Normal file
3
fastlane/metadata/android/fr-FR/changelogs/136.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
* Correction UnlockManager #2098 #2101
|
||||
* Invite de déverrouillage automatique de l'appareil #2105
|
||||
* Petites corrections ##2066
|
||||
@@ -2,19 +2,19 @@ KeePassDX est un <b>coffre-fort et un gestionnaire de mots de passe</b> qui perm
|
||||
|
||||
<b>Fonctionnalités</b>
|
||||
|
||||
- Création de bases de données / entrées et groupes.
|
||||
- Support des fichiers .kdb et .kdbx (version 1 à 4) avec algorithme AES - Twofish - ChaCha20 - Argon2.
|
||||
- Compatible avec la majorité des programmes alternatifs (KeePass, KeePassXC, KeeWeb, …)
|
||||
- Permet la copie rapide de champs et l'ouverture d'URI /URL.
|
||||
- Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / …).
|
||||
- Gestion des mots de passe à usage unique (One-Time Password HOTP / TOTP) pour l'authentification à deux facteurs (2FA).
|
||||
- Material design avec thèmes.
|
||||
- Remplissage automatique de champs.
|
||||
- Clavier de remplissage de champs.
|
||||
- Gabarits dynamiques.
|
||||
- Historique de chaque entrée.
|
||||
- Gestion précise des paramètres.
|
||||
- Code écrit en langages natifs (Kotlin / Java / JNI / C).
|
||||
- Création de bases de données / entrées et groupes.
|
||||
- Support des fichiers .kdb et .kdbx (version 1 à 4) avec algorithme AES - Twofish - ChaCha20 - Argon2.
|
||||
- Compatible avec la majorité des programmes alternatifs (KeePass, KeePassXC, KeeWeb, …)
|
||||
- Permet la copie rapide de champs et l'ouverture d'URI /URL.
|
||||
- Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / …).
|
||||
- Gestion des mots de passe à usage unique (One-Time Password HOTP / TOTP) pour l'authentification à deux facteurs (2FA).
|
||||
- Material design avec thèmes.
|
||||
- Remplissage automatique de champs.
|
||||
- Clavier de remplissage de champs.
|
||||
- Gabarits dynamiques.
|
||||
- Historique de chaque entrée.
|
||||
- Gestion précise des paramètres.
|
||||
- Code écrit en langages natifs (Kotlin / Java / JNI / C).
|
||||
|
||||
Vous pouvez faire un don ou acheter la version pro pour un meilleur service et un développement rapide des fonctionnalités que vous souhaitez : <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
Coffre-fort et gestionnaire de mots de passe open source sécurisé
|
||||
Coffre-fort et gestionnaire de mots de passe open source sécurisé
|
||||
@@ -1 +1 @@
|
||||
KeePassDX - Coffre fort de Mdp
|
||||
KeePassDX - Coffre fort de Mdp
|
||||
@@ -1,19 +1,20 @@
|
||||
KeePassDX は <b>オープンソース</b>かつ<b>広告なし</b>です。<b>複数の形式に対応する KeePass パスワード マネージャー</b>。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。<b>インターネットに接続する必要はありません。</b>
|
||||
KeePassDXは<b>パスワードセーフ兼マネージャー</b>で、オープンなKeePass形式で<b>暗号化されたデータを単一ファイルとして編集</b>したり、<b>安全な方法でフォームに入力</b>したりできます。<b>インターネット接続が不要</b>で、Androidの標準デザインへ準拠しています。このアプリは<b>オープンソースで広告がありません</b>。
|
||||
|
||||
<b>機能</b>
|
||||
- データベースファイル / エントリー・グループの作成
|
||||
- .kdb、.kdbx ファイル(バージョン1から4)に対応。AES、Twofish、ChaCha20、Argon2 アルゴリズムが使用可能
|
||||
- 主流の代替ソフトウェア(KeePass、KeePassXC, KeeWeb など)との互換性あり
|
||||
- URI / URL フィールドは開くのもコピーするのもすばやく行えます
|
||||
- 生体認証を使った高速ロック解除 (指紋認証 / 顔認証 / …)
|
||||
- 2 要素認証(2FA)のためのワンタイムパスワード管理(HOTP / TOTP)
|
||||
- マテリアルデザインに準拠した複数のテーマ
|
||||
- 自動入力機能の統合
|
||||
- フィールド入力用のキーボード
|
||||
- ダイナミックテンプレート
|
||||
- エントリーごとの履歴
|
||||
- 設定の細かな管理
|
||||
- コードはネイティブ言語(Kotlin / Java / JNI / C)で書かれています
|
||||
|
||||
- データベースファイル / エントリー・グループの作成
|
||||
- .kdb、.kdbx ファイル(バージョン1から4)に対応。AES、Twofish、ChaCha20、Argon2 アルゴリズムが使用可能
|
||||
- 主流の代替ソフトウェア(KeePass、KeePassXC, KeeWeb など)との互換性あり
|
||||
- URI / URL フィールドは開くのもコピーするのもすばやく行えます
|
||||
- 生体認証を使った高速ロック解除 (指紋認証 / 顔認証 / …)
|
||||
- 2 要素認証(2FA)のためのワンタイムパスワード管理(HOTP / TOTP)
|
||||
- マテリアルデザインに準拠した複数のテーマ
|
||||
- 自動入力機能の統合
|
||||
- フィールド入力用のキーボード
|
||||
- ダイナミックテンプレート
|
||||
- エントリーごとの履歴
|
||||
- 設定の細かな管理
|
||||
- コードはネイティブ言語(Kotlin / Java / JNI / C)で書かれています
|
||||
|
||||
寄付または pro バージョンの購入はサービスの改善と必要な機能の迅速な開発につながります: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
1
fastlane/metadata/android/ja-JP/title.txt
Normal file
1
fastlane/metadata/android/ja-JP/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
KeePassDX - FOSS パスワードセーフ
|
||||
23
fastlane/metadata/android/mk-MK/full_description.txt
Normal file
23
fastlane/metadata/android/mk-MK/full_description.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
KeePassDX е <b>безбеден и менаџер за лозинки</b> кој овозможува уредување на <b>шифрирани податоци во една датотека</b> во отворен KeePass формат и <b>пополнување на формуларите на безбеден начин</b>, <b>не бара интернет конекција</b> и ги интегрира стандардите за дизајн на Android. Апликацијата е <b>отворен код, без рекламирање</b>.
|
||||
|
||||
<b>Карактеристики</b>
|
||||
|
||||
- Креирање датотеки / записи и групи во базата на податоци.
|
||||
- Поддршка за .kdb и .kdbx датотеки (верзија од 1 до 4) со AES - Twofish - ChaCha20 - Argon2 алгоритам.
|
||||
- Компатибилен со повеќето алтернативни програми (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Овозможува брзо отворање и копирање на URI / URL полиња.
|
||||
- Биометриско препознавање за брзо отклучување (отпечаток од прст / отклучување со лице / …).
|
||||
- Еднократно управување со лозинки (HOTP / TOTP) за двофакторска автентикација (2FA).
|
||||
- Дизајн на материјали со теми.
|
||||
- Автоматско пополнување и интеграција.
|
||||
- Тастатура за пополнување полиња.
|
||||
- Динамични шаблони.
|
||||
- Историја на секој запис.
|
||||
- Прецизно управување со поставките.
|
||||
- Код напишан на мајчини јазици (Kotlin / Java / JNI / C).
|
||||
|
||||
Можете да донирате или купите про верзијата за подобра услуга и брз развој на функциите што ги сакате: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
Проектот постојано се развива. Не двоумете се да го проверите статусот на развој на следните ажурирања: <a href="https://github.com/Kunzisoft/KeePassDX/projects">https://github.com/Kunzisoft/KeePassDX/projects</a>
|
||||
|
||||
Испратете ги проблемите до: <a href="https://github.com/Kunzisoft/KeePassDX/issues">https://github.com/Kunzisoft/KeePassDX/issues</a>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user