Compare commits

...

209 Commits

Author SHA1 Message Date
J-Jamet
2928b7daa3 Merge branch 'release/2.9.11' 2021-01-16 19:09:22 +01:00
J-Jamet
3a55dea276 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2021-01-16 18:54:47 +01:00
J-Jamet
2a25213d66 Update CHANGELOG 2021-01-16 18:49:11 +01:00
J-Jamet
035ffd8135 Fix hexadecimal keyfile #861 2021-01-16 18:46:17 +01:00
J-Jamet
b040487f1f Use decodeHex method 2021-01-15 18:16:46 +01:00
J-Jamet
6fc821aecf Fix keyx file version 2 #844 2021-01-15 17:00:29 +01:00
J-Jamet
cdceb1fb6f Fix keyx file version 2 #844 2021-01-15 16:57:29 +01:00
J-Jamet
07d185913d Upgrade to 2.9.11 2021-01-15 16:35:25 +01:00
Eric
f2a245a9c8 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (507 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-01-15 14:19:14 +01:00
Oliver Cervera
33338f4759 Translated using Weblate (Italian)
Currently translated at 100.0% (507 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-01-15 14:19:13 +01:00
J-Jamet
f7a4370b29 Merge tag '2.9.10' into develop
2.9.10
2021-01-15 01:53:07 +01:00
J-Jamet
77b7afedda Merge branch 'release/2.9.10' 2021-01-15 01:52:59 +01:00
J-Jamet
caa13039e5 Update CHANGELOG 2021-01-15 01:52:32 +01:00
J-Jamet
02845d93ed Change order keyfile recognition 2021-01-15 01:44:40 +01:00
J-Jamet
9ef4695cc7 Change order keyfile recognition 2021-01-15 01:00:13 +01:00
Oğuz Ersen
d619e089c0 Translated using Weblate (Turkish)
Currently translated at 100.0% (507 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-01-14 23:11:57 +01:00
Ihor Hordiichuk
3c50348a79 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (507 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-01-14 23:11:56 +01:00
solokot
167ea3b82b Translated using Weblate (Russian)
Currently translated at 100.0% (507 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-01-14 23:11:56 +01:00
WaldiS
9eda3e62f7 Translated using Weblate (Polish)
Currently translated at 99.0% (502 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-01-14 23:11:56 +01:00
Kunzisoft
99c4319b51 Translated using Weblate (French)
Currently translated at 100.0% (507 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2021-01-14 23:11:55 +01:00
Retrial
790b25db65 Translated using Weblate (Greek)
Currently translated at 100.0% (507 of 507 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-01-14 23:11:55 +01:00
J-Jamet
97d4972f9a Try to fix crash with autofill #852 2021-01-14 21:42:40 +01:00
J-Jamet
8e6853756f Upgrade to version 2.9.10 2021-01-14 14:58:07 +01:00
J-Jamet
6d3aae187b Merge tag '2.9.9' into develop
2.9.9
2021-01-14 14:46:53 +01:00
J-Jamet
b8c7acf7ce Merge branch 'release/2.9.9' 2021-01-14 14:46:46 +01:00
J-Jamet
17a356ae76 Replace strong tag 2021-01-14 13:50:41 +01:00
J-Jamet
bd847e632d Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2021-01-14 13:46:56 +01:00
J-Jamet
2bfb9b048d Better entry visualisation 2021-01-14 13:23:35 +01:00
J-Jamet
dc40b50b65 Parse new TOTP fields from KeePass 2.47 #850 2021-01-14 11:48:44 +01:00
J-Jamet
3e2271e596 Update CHANGELOG 2021-01-13 16:48:36 +01:00
J-Jamet
4b4fd2a11d Fix OTP generation for long secret key #848 2021-01-13 16:46:19 +01:00
J-Jamet
23468290df Fix small visual element 2021-01-12 19:43:16 +01:00
J-Jamet
a276f6aa06 Remove keyfile icon 2021-01-12 19:11:23 +01:00
J-Jamet
f2a58361a1 Update CHANGELOG 2021-01-12 18:09:40 +01:00
J-Jamet
271023b528 Fix Toggling custom field protection #849 2021-01-12 18:08:05 +01:00
J-Jamet
e1771ca249 Upgrade CHANGELOG 2021-01-12 15:11:09 +01:00
J-Jamet
ca4f4bd151 Add priority to OTP button in notification #845 2021-01-12 15:08:59 +01:00
J-Jamet
d81454d618 FEATURE_SECURE_PROCESSING Error to Warning log 2021-01-12 13:13:08 +01:00
J-Jamet
fb43c1c624 Special search in title fields #830 2021-01-12 09:28:05 +01:00
J-Jamet
9e060f878d Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2021-01-12 09:24:09 +01:00
Milo Ivir
bd9c21ee8a Translated using Weblate (Croatian)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-01-12 00:32:09 +01:00
J-Jamet
3e6d40e8da Better autofill suggestion toast 2021-01-11 21:33:25 +01:00
J-Jamet
79683cb3fc Encapsulate autofillInlineSuggestionsEnabled preference 2021-01-11 21:17:34 +01:00
J-Jamet
52a2090a31 Upgrade CHANGELOG 2021-01-11 20:53:52 +01:00
J-Jamet
3dfe4ace7b Fix binary keyfiles of 64 bytes #835 2021-01-11 20:42:14 +01:00
J-Jamet
bd0d17b134 Remove unused log 2021-01-11 17:58:22 +01:00
J-Jamet
6b0ccc1780 Update CHANGELOG 2021-01-11 15:02:18 +01:00
J-Jamet
d75ac4b825 Remove unused log 2021-01-11 14:55:37 +01:00
J-Jamet
b60d610d02 Remove loadXmlKeyFile method in KDB database 2021-01-11 14:30:53 +01:00
J-Jamet
f7a5c5d0ea Fix hash in keyfile XML version 1 2021-01-11 14:17:43 +01:00
J-Jamet
28f79aec11 Check keyfile XML hash 2021-01-11 14:11:30 +01:00
J-Jamet
778d963fbf Encapsulate String util and fix Key File recognition #844 2021-01-11 11:31:22 +01:00
J-Jamet
a765bc84e7 Upgrade buildToolsVersion to 30.0.3 2021-01-11 11:29:31 +01:00
J-Jamet
804ecc1baa Merge branch 'feature/Autofill_Inline' into develop #827 2021-01-09 16:01:04 +01:00
J-Jamet
d331c3dc03 Fix title in autofill activity 2021-01-09 16:00:44 +01:00
J-Jamet
7010d2f86a Refresh preferences during connection 2021-01-09 15:50:50 +01:00
J-Jamet
b1d6117eb2 Fix autofill longpress 2021-01-09 15:44:47 +01:00
J-Jamet
f3b814388d Add toast to inform the user of inline suggestions in the keyboard 2021-01-09 15:02:29 +01:00
J-Jamet
b62996a57c Setting to allow or not inline suggestions 2021-01-09 14:29:59 +01:00
J-Jamet
a49e056f02 Autofill component to select entry with inline response 2021-01-09 14:02:01 +01:00
J-Jamet
a6dece16bf Merge branch 'develop' into feature/Autofill_Inline 2021-01-09 12:33:26 +01:00
J-Jamet
8e3ddd64d2 Better exception catching #794 2021-01-09 12:17:43 +01:00
J-Jamet
45a847fa3e Remove unused code 2021-01-09 11:26:19 +01:00
J-Jamet
6b6f03b143 Remove small warning 2021-01-09 11:20:41 +01:00
J-Jamet
5446efca4a Output header refactor 2021-01-09 11:09:31 +01:00
J-Jamet
8d04a7f90b Merge branch 'develop' into feature/Autofill_Inline 2021-01-08 16:36:34 +01:00
J-Jamet
626495c19e Upgrade CHANGELOG 2021-01-07 23:35:06 +01:00
J-Jamet
e5a198f524 Remove unused variable 2021-01-07 23:22:30 +01:00
J-Jamet
161524843f Merge branch 'feature/Detect_File_Changes' into develop #794 2021-01-07 22:50:05 +01:00
J-Jamet
5550e7dea3 Remove unused duplicateUUID exception during reloading 2021-01-07 22:44:46 +01:00
J-Jamet
64f66c290c Prevent reloading from special mode 2021-01-07 22:34:11 +01:00
J-Jamet
e8925b3c0b Fix exception 2021-01-07 22:12:02 +01:00
J-Jamet
cf67ce04a8 Better animation and reload setting screen 2021-01-07 22:03:17 +01:00
J-Jamet
84ee4ca2c7 Change dialog 2021-01-07 21:07:32 +01:00
J-Jamet
27eb095720 Add database reloading #794 2021-01-07 16:25:05 +01:00
J-Jamet
d273f21819 Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2021-01-05 15:50:34 +01:00
J-Jamet
455fd0cd6d Remove unused log 2021-01-05 13:20:12 +01:00
J-Jamet
c5a8650c81 Show a dialog when a database file info changes #794 2021-01-05 12:48:06 +01:00
J-Jamet
b5f9bbed5e Detect Database File Info Changes #794 2021-01-04 19:26:20 +01:00
George
e789741090 Translated using Weblate (Bulgarian)
Currently translated at 2.2% (11 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bg/
2021-01-04 03:29:34 +01:00
George
5c6d93bc57 Added translation using Weblate (Bulgarian) 2021-01-03 02:39:46 +01:00
WaldiS
697b672038 Translated using Weblate (Polish)
Currently translated at 98.8% (494 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-01-02 22:49:58 +01:00
Y. Sakamoto
2d9e9c24a8 Translated using Weblate (Japanese)
Currently translated at 99.8% (499 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-01-02 22:49:58 +01:00
J-Jamet
5fb281c800 Upgrade to 2.9.9 2021-01-02 19:13:08 +01:00
J-Jamet
96896c1c42 Merge tag '2.9.8' into develop
2.9.8
2021-01-02 18:28:48 +01:00
J-Jamet
d7052bd9e6 Merge branch 'release/2.9.8' 2021-01-02 18:28:26 +01:00
J-Jamet
8b23932788 Fix IllegalStateException 2021-01-02 18:28:03 +01:00
J-Jamet
50912c6966 Update CHANGELOG 2021-01-02 18:25:45 +01:00
J-Jamet
53b51934b9 Upgrade kotlin version 2021-01-02 18:14:15 +01:00
J-Jamet
a8a3685965 Upgrade Room to 2.2.6 2021-01-02 13:50:38 +01:00
J-Jamet
149b67e28b Better exception StreamCipherFactory.getInstance management 2021-01-02 11:04:45 +01:00
J-Jamet
a83032bffa Fix binary in a single entry #828 2021-01-02 09:44:03 +01:00
Oğuz Ersen
a5d3a153bf Translated using Weblate (Turkish)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-12-31 13:30:01 +01:00
Eric
4210c155eb Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-12-31 13:30:01 +01:00
Ihor Hordiichuk
4bf110a9b1 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-12-31 13:30:00 +01:00
solokot
50f2684500 Translated using Weblate (Russian)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-12-31 13:30:00 +01:00
J. Lavoie
e95424b8f9 Translated using Weblate (Italian)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-12-31 13:30:00 +01:00
J. Lavoie
8462882707 Translated using Weblate (French)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-12-31 13:30:00 +01:00
Óscar Fernández Díaz
a5c8d25f64 Translated using Weblate (Spanish)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-31 13:29:59 +01:00
Retrial
689ce2f9b3 Translated using Weblate (Greek)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-12-31 13:29:59 +01:00
J. Lavoie
54246533ac Translated using Weblate (German)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-31 13:29:59 +01:00
zeritti
66e4b0fe47 Translated using Weblate (Czech)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-12-31 13:29:16 +01:00
J-Jamet
3e8ae3e2e3 Upgrade KeePassDX Pro description 2020-12-30 17:23:17 +01:00
J-Jamet
d856ef3772 Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2020-12-30 17:20:05 +01:00
x
5727880ac7 Translated using Weblate (Italian)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-12-30 03:21:37 +01:00
Oliver Cervera
ec4302a780 Translated using Weblate (Italian)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-12-30 03:20:48 +01:00
x
d4203598a1 Translated using Weblate (Italian)
Currently translated at 100.0% (500 of 500 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-12-30 03:20:46 +01:00
Hosted Weblate
a278c8c718 Merge branch 'origin/develop' into Weblate. 2020-12-28 21:11:52 +01:00
WaldiS
faf5f4b51a Translated using Weblate (Polish)
Currently translated at 98.7% (492 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-12-28 21:11:52 +01:00
J-Jamet
b2f503b326 Upgrade to 2.9.8 2020-12-28 21:11:20 +01:00
J-Jamet
beb5484bf6 Merge tag '2.9.7' into develop
2.9.7
2020-12-28 21:00:32 +01:00
J-Jamet
ec63d75349 Merge branch 'release/2.9.7' 2020-12-28 21:00:27 +01:00
J-Jamet
4c0e79b245 Update CHANGELOG 2020-12-28 20:46:14 +01:00
J-Jamet
50a77684c1 Replace strong tag 2020-12-28 20:35:20 +01:00
J-Jamet
8bb84b486d Fix small translation 2020-12-28 20:33:41 +01:00
J-Jamet
4b05f2536f Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2020-12-28 20:33:05 +01:00
J-Jamet
d6f968fe7e Write permission until Android 10 #823 2020-12-28 12:06:48 +01:00
J-Jamet
ed758edd44 Fix small warning 2020-12-28 11:57:29 +01:00
J-Jamet
94b7fce2e5 Merge branch 'tibequadorian-patch-1' into develop 2020-12-28 11:54:56 +01:00
J-Jamet
dbd9c6cbb7 Fix rebuiltList crash 2020-12-28 11:39:16 +01:00
J-Jamet
0f6376fb80 Fix illegalstate when managing views 2020-12-28 11:17:38 +01:00
J-Jamet
9522328238 Fix crash when creating new field 2020-12-28 11:05:20 +01:00
J-Jamet
e6ad716119 Fix crash 2020-12-28 10:45:27 +01:00
Éfrit
440006bb08 Translated using Weblate (French)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-12-26 23:29:31 +01:00
tibequadorian
ea289ef7cf fix typo 2020-12-26 05:42:57 +01:00
solokot
352b171484 Translated using Weblate (Russian)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-12-25 17:10:11 +01:00
Óscar Fernández Díaz
969ab56bf8 Translated using Weblate (Spanish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-25 00:29:12 +01:00
J-Jamet
062a9852e5 Fix small warning 2020-12-24 15:20:13 +01:00
Óscar Fernández Díaz
dd77d7a5e6 Translated using Weblate (Spanish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 23:22:51 +01:00
SeerLite
f60e32522a Translated using Weblate (Spanish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 23:22:51 +01:00
Óscar Fernández Díaz
070a91f19c Translated using Weblate (Spanish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 23:20:44 +01:00
SeerLite
0790e80670 Translated using Weblate (Spanish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 23:20:44 +01:00
Óscar Fernández Díaz
20841e3d7b Translated using Weblate (Spanish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 23:13:36 +01:00
SeerLite
0caae233c3 Translated using Weblate (Spanish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 23:13:36 +01:00
Óscar Fernández Díaz
5497d8fafb Translated using Weblate (Spanish)
Currently translated at 99.5% (496 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:41:42 +01:00
SeerLite
f69b43249c Translated using Weblate (Spanish)
Currently translated at 99.5% (496 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:41:41 +01:00
Óscar Fernández Díaz
b606fd98f6 Translated using Weblate (Spanish)
Currently translated at 88.5% (441 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:20:52 +01:00
SeerLite
ba3b7b0f1f Translated using Weblate (Spanish)
Currently translated at 88.5% (441 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:20:52 +01:00
Óscar Fernández Díaz
058d82dc36 Translated using Weblate (Spanish)
Currently translated at 86.3% (430 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:17:07 +01:00
SeerLite
6f0b0ac4fa Translated using Weblate (Spanish)
Currently translated at 86.3% (430 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:17:06 +01:00
Óscar Fernández Díaz
35f87b0f94 Translated using Weblate (Spanish)
Currently translated at 85.5% (426 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:15:19 +01:00
SeerLite
0ead9ce9b4 Translated using Weblate (Spanish)
Currently translated at 85.5% (426 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-12-23 17:15:18 +01:00
vachan-maker
80479a6a7c Translated using Weblate (Malayalam)
Currently translated at 76.9% (383 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ml/
2020-12-22 00:37:38 +01:00
J-Jamet
a7cea8201e Try to fix biometric crash 2020-12-20 11:41:58 +01:00
solokot
081a7fa798 Translated using Weblate (Russian)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-12-20 10:29:12 +01:00
WaldiS
85782c4f93 Translated using Weblate (Polish)
Currently translated at 97.9% (488 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-12-20 10:29:11 +01:00
uniprivscy
d7b7df26d7 Translated using Weblate (German)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-18 18:29:10 +01:00
J-Jamet
b6b1c8e31d Upgrade to version 2.9.7 2020-12-18 10:51:42 +01:00
J-Jamet
17156f7ca2 Merge tag '2.9.6' into develop
2.9.6
2020-12-18 10:15:51 +01:00
J-Jamet
0761d356b8 Merge branch 'release/2.9.6' 2020-12-18 10:15:41 +01:00
J-Jamet
6da747ce6f Fix keyfile bug #820 2020-12-18 10:06:38 +01:00
uniprivscy
87b1a1f527 Translated using Weblate (German)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-17 17:44:09 +01:00
Paul
72a8a55faf Translated using Weblate (German)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-17 17:44:09 +01:00
J-Jamet
9a6a709746 Inline presentation when sign in 2020-12-17 15:16:06 +01:00
J-Jamet
428b53cc56 Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2020-12-17 12:32:37 +01:00
J-Jamet
e688859e32 Fix exception when UI not fully loaded and click performed 2020-12-17 12:32:30 +01:00
Milo Ivir
98336da116 Translated using Weblate (Croatian)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-12-17 09:56:23 +01:00
Oğuz Ersen
c037e443b0 Translated using Weblate (Turkish)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-12-17 09:56:23 +01:00
Eric
d339a50e0a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-12-17 09:56:23 +01:00
Ihor Hordiichuk
7d836f2633 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-12-17 09:56:22 +01:00
solokot
45d8470b4c Translated using Weblate (Russian)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-12-17 09:56:22 +01:00
Oliver Cervera
1ca3bfe472 Translated using Weblate (Italian)
Currently translated at 99.7% (497 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-12-17 09:56:22 +01:00
Retrial
066da83d70 Translated using Weblate (Greek)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-12-17 09:56:22 +01:00
zeritti
44ab881751 Translated using Weblate (Czech)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-12-17 09:56:21 +01:00
J-Jamet
5ab3cf985a Merge branch 'develop' into feature/Autofill_Inline 2020-12-16 17:54:53 +01:00
J-Jamet
f271f2b181 Update to 2.9.6 2020-12-16 17:54:33 +01:00
J-Jamet
91d75be0ea Merge tag '2.9.5' into develop
2.9.5
2020-12-16 17:06:02 +01:00
J-Jamet
774dddca54 Merge branch 'release/2.9.5' 2020-12-16 17:05:47 +01:00
J-Jamet
e18b3436c9 Add inline autofill right icon 2020-12-16 15:37:23 +01:00
J-Jamet
fcb1b5ae6b First inline code 2020-12-16 15:25:04 +01:00
J-Jamet
de980d030a Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-12-16 11:13:48 +01:00
J-Jamet
0e859646fe Fix timeout reset #817 2020-12-15 19:40:27 +01:00
christopher robert
059c7b7713 Translated using Weblate (German)
Currently translated at 98.3% (490 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-15 13:29:10 +01:00
J-Jamet
5fb7bf71c8 Prevent auto switch back to previous keyboard if otp field exists #814 2020-12-15 11:47:57 +01:00
J-Jamet
8b0133ff7f Update CHANGELOG 2020-12-14 19:25:34 +01:00
J-Jamet
8d834946b8 Fix view flickering 2020-12-14 19:12:45 +01:00
J-Jamet
2f646395d4 Merge branch 'feature/Device_Unlock' into develop 2020-12-14 18:31:28 +01:00
J-Jamet
f6e79ba37b Add advanced unlock education hint 2020-12-14 18:23:41 +01:00
J-Jamet
e633c7a861 Biometric unlock in priority and device unlock when biometric not available 2020-12-14 17:51:18 +01:00
J-Jamet
dc02a8d78c Rollback to fix bug after orientation change 2020-12-14 16:59:38 +01:00
J-Jamet
baa9b88512 Check if current database is the same after activity result 2020-12-14 16:56:15 +01:00
J-Jamet
c522e87da8 Fix multiple methods in settings 2020-12-14 16:41:14 +01:00
Paul
ef5ebf2c15 Translated using Weblate (German)
Currently translated at 98.3% (490 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-14 13:23:34 +01:00
christopher robert
4b147e770c Translated using Weblate (German)
Currently translated at 98.3% (490 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-12-14 13:23:34 +01:00
J-Jamet
157a5c0b05 Refactor view visibility method 2020-12-13 23:39:17 +01:00
J-Jamet
f2288b0c64 Fix biometric prompt during orientation change 2020-12-13 23:25:44 +01:00
J-Jamet
d8506450aa Fix biometric orientation change 2020-12-13 23:19:04 +01:00
J-Jamet
f9b085e73f Fix min setting version and condition in Android R 2020-12-13 22:44:30 +01:00
J-Jamet
388cf6a91b Fix device credential condition and keep connexion if ActivityForResult requested 2020-12-13 22:33:58 +01:00
Stephan Paternotte
9e6e77b363 Translated using Weblate (Dutch)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-12-13 18:48:46 +01:00
J-Jamet
ec33ca8173 Refactoring Advanced unlock fragment and manager 2020-12-13 17:09:41 +01:00
J-Jamet
6be0457947 Add fragment 2020-12-13 14:18:21 +01:00
WaldiS
f3b84aa845 Translated using Weblate (Polish)
Currently translated at 98.1% (489 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-12-12 17:29:08 +01:00
J-Jamet
bd0b5b0954 Fix exception message 2020-12-12 14:53:23 +01:00
J-Jamet
7dc93604ad Refactoring AdvancedUnlockManager.kt 2020-12-12 14:13:13 +01:00
J-Jamet
0ab22698a6 Refactoring classes 2020-12-11 15:15:52 +01:00
J-Jamet
c885ce7aaf Refactoring of advanced unlock 2020-12-11 13:36:51 +01:00
J-Jamet
92d1a7b901 Fix string 2020-12-11 11:11:48 +01:00
J-Jamet
6119054b45 Upgrade to version 2.9.5 2020-12-11 11:03:00 +01:00
Eric
e7aed72398 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-12-11 01:55:56 +01:00
solokot
cee7fa50f5 Translated using Weblate (Russian)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-12-11 01:55:55 +01:00
Stephan Paternotte
39a38bb223 Translated using Weblate (Dutch)
Currently translated at 100.0% (498 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-12-11 01:55:55 +01:00
Oliver Cervera
7159a993db Translated using Weblate (Italian)
Currently translated at 99.7% (497 of 498 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-12-11 01:55:54 +01:00
J-Jamet
23933e80e3 Merge tag '2.9.4' into develop
2.9.4
2020-12-10 22:27:38 +01:00
J-Jamet
abc971b5cc Merge branch 'release/2.9.4' 2020-12-10 22:27:30 +01:00
J-Jamet
7dedcc8a21 Argon2_id implementation #791 2020-12-10 22:15:08 +01:00
J-Jamet
10d46e5dee Remove default device credential in Android R to prevent update bug #812 2020-12-10 14:58:33 +01:00
J-Jamet
139f7eb36d Upgrade version to 2.9.4 2020-12-09 16:50:32 +01:00
J-Jamet
1ddfa894b6 Merge tag '2.9.3' into develop
2.9.3
2020-12-09 16:13:56 +01:00
131 changed files with 3274 additions and 1759 deletions

View File

@@ -1,3 +1,41 @@
KeePassDX(2.9.11)
* Add Keyfile XML version 2 (fix hex) #844
* Fix hex Keyfile #861
KeePassDX(2.9.10)
* Try to fix autofill #852
* Fix database change dialog displayed too often #853
KeePassDX(2.9.9)
* Detect file changes and reload database #794
* Inline suggestions autofill with compatible keyboard (Android R) #827
* Add Keyfile XML version 2 #844
* Fix binaries of 64 bytes #835
* Special search in title fields #830
* Priority to OTP button in notifications #845
* Fix OTP generation for long secret key #848
* Fix small bugs #849
KeePassDX(2.9.8)
* Fix specific attachments with kdbx3.1 databases #828
* Fix small bugs
KeePassDX(2.9.7)
* Remove write permission since Android 10 #823
* Fix small bugs
KeePassDX(2.9.6)
* Fix KeyFile bug #820
KeePassDX(2.9.5)
* Unlock database by device credentials (PIN/Password/Pattern) with Android M+ #102 #152 #811
* Prevent auto switch back to previous keyboard if otp field exists #814
* Fix timeout reset #817
KeePassDX(2.9.4)
* Fix small bugs #812
* Argon2ID implementation #791
KeePassDX(2.9.3) KeePassDX(2.9.3)
* Unlock database by device credentials (PIN/Password/Pattern) #779 #102 * Unlock database by device credentials (PIN/Password/Pattern) #779 #102
* Advanced unlock with timeout #102 #437 #566 * Advanced unlock with timeout #102 #437 #566

View File

@@ -5,15 +5,15 @@ apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.2' buildToolsVersion '30.0.3'
ndkVersion '21.3.6528147' ndkVersion '21.3.6528147'
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 30 targetSdkVersion 30
versionCode = 47 versionCode = 55
versionName = "2.9.3" versionName = "2.9.11"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"
@@ -92,7 +92,7 @@ android {
} }
} }
def room_version = "2.2.5" def room_version = "2.2.6"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@@ -110,6 +110,8 @@ dependencies {
// Database // Database
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
// Autofill
implementation "androidx.autofill:autofill:1.1.0-rc01"
// Crypto // Crypto
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01' implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
// Time // Time
@@ -121,7 +123,7 @@ dependencies {
// Apache Commons Collections // Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.2' implementation 'commons-collections:commons-collections:3.2.2'
// Apache Commons Codec // Apache Commons Codec
implementation 'commons-codec:commons-codec:1.14' implementation 'commons-codec:commons-codec:1.15'
// Icon pack // Icon pack
implementation project(path: ':icon-pack-classic') implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material') implementation project(path: ':icon-pack-material')

View File

@@ -14,10 +14,15 @@
android:name="android.permission.USE_BIOMETRIC" /> android:name="android.permission.USE_BIOMETRIC" />
<uses-permission <uses-permission
android:name="android.permission.VIBRATE"/> android:name="android.permission.VIBRATE"/>
<!-- Write permission until Android 10 -->
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
<!-- Open apps from links -->
<uses-permission <uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"/> android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application <application
android:label="@string/app_name" android:label="@string/app_name"

View File

@@ -26,6 +26,7 @@ import android.content.Intent
import android.content.IntentSender import android.content.IntentSender
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -33,6 +34,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.EXTRA_INLINE_SUGGESTIONS_REQUEST
import com.kunzisoft.keepass.autofill.KeeAutofillService import com.kunzisoft.keepass.autofill.KeeAutofillService
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.search.SearchHelper
@@ -40,7 +42,6 @@ import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION import com.kunzisoft.keepass.utils.LOCK_ACTION
import com.kunzisoft.keepass.utils.UriUtil
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() { class AutofillLauncherActivity : AppCompatActivity() {
@@ -84,9 +85,9 @@ class AutofillLauncherActivity : AppCompatActivity() {
private fun launchSelection(searchInfo: SearchInfo) { private fun launchSelection(searchInfo: SearchInfo) {
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE) // Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val assistStructure = AutofillHelper.retrieveAssistStructure(intent) val autofillComponent = AutofillHelper.retrieveAutofillComponent(intent)
if (assistStructure == null) { if (autofillComponent == null) {
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_CANCELED)
finish() finish()
} else if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId, } else if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId,
@@ -105,21 +106,21 @@ class AutofillLauncherActivity : AppCompatActivity() {
searchInfo, searchInfo,
{ items -> { items ->
// Items found // Items found
AutofillHelper.buildResponse(this, items) AutofillHelper.buildResponseAndSetResult(this, items)
finish() finish()
}, },
{ {
// Show the database UI to select the entry // Show the database UI to select the entry
GroupActivity.launchForAutofillResult(this, GroupActivity.launchForAutofillResult(this,
readOnly, readOnly,
assistStructure, autofillComponent,
searchInfo, searchInfo,
false) false)
}, },
{ {
// If database not open // If database not open
FileDatabaseSelectActivity.launchForAutofillResult(this, FileDatabaseSelectActivity.launchForAutofillResult(this,
assistStructure, autofillComponent,
searchInfo) searchInfo)
} }
) )
@@ -196,7 +197,8 @@ class AutofillLauncherActivity : AppCompatActivity() {
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO" private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
fun getAuthIntentSenderForSelection(context: Context, fun getAuthIntentSenderForSelection(context: Context,
searchInfo: SearchInfo? = null): IntentSender { searchInfo: SearchInfo? = null,
inlineSuggestionsRequest: InlineSuggestionsRequest? = null): IntentSender {
return PendingIntent.getActivity(context, 0, return PendingIntent.getActivity(context, 0,
// Doesn't work with Parcelable (don't know why?) // Doesn't work with Parcelable (don't know why?)
Intent(context, AutofillLauncherActivity::class.java).apply { Intent(context, AutofillLauncherActivity::class.java).apply {
@@ -205,6 +207,11 @@ class AutofillLauncherActivity : AppCompatActivity() {
putExtra(KEY_SEARCH_DOMAIN, it.webDomain) putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
putExtra(KEY_SEARCH_SCHEME, it.webScheme) putExtra(KEY_SEARCH_SCHEME, it.webScheme)
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
}
}
}, },
PendingIntent.FLAG_CANCEL_CURRENT).intentSender PendingIntent.FLAG_CANCEL_CURRENT).intentSender
} }

View File

@@ -39,7 +39,9 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
@@ -52,7 +54,9 @@ import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
@@ -133,7 +137,7 @@ class EntryActivity : LockingActivity() {
} }
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout) coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
// Init the clipboard helper // Init the clipboard helper
clipboardHelper = ClipboardHelper(this) clipboardHelper = ClipboardHelper(this)
@@ -150,6 +154,10 @@ class EntryActivity : LockingActivity() {
if (result.isSuccess) if (result.isSuccess)
finish() finish()
} }
ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
finish()
}
} }
coordinatorLayout?.showActionError(result) coordinatorLayout?.showActionError(result)
} }
@@ -198,8 +206,7 @@ class EntryActivity : LockingActivity() {
// Refresh Menu // Refresh Menu
invalidateOptionsMenu() invalidateOptionsMenu()
val entryInfo = entry.getEntryInfo(Database.getInstance()) val entryInfo = entry.getEntryInfo(mDatabase)
// Manage entry copy to start notification if allowed // Manage entry copy to start notification if allowed
if (mFirstLaunchOfActivity) { if (mFirstLaunchOfActivity) {
// Manage entry to launch copying notification if allowed // Manage entry to launch copying notification if allowed
@@ -231,23 +238,21 @@ class EntryActivity : LockingActivity() {
private fun fillEntryDataInContentsView(entry: Entry) { private fun fillEntryDataInContentsView(entry: Entry) {
val database = Database.getInstance() val entryInfo = entry.getEntryInfo(mDatabase)
database.startManageEntry(entry)
// Assign title icon // Assign title icon
titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor) titleIconView?.assignDatabaseIcon(mDatabase!!.drawFactory, entryInfo.icon, iconColor)
// Assign title text // Assign title text
val entryTitle = entry.title val entryTitle = entryInfo.title
collapsingToolbarLayout?.title = entryTitle collapsingToolbarLayout?.title = entryTitle
toolbar?.title = entryTitle toolbar?.title = entryTitle
// Assign basic fields // Assign basic fields
entryContentsView?.assignUserName(entry.username) { entryContentsView?.assignUserName(entryInfo.username) {
database.startManageEntry(entry) clipboardHelper?.timeoutCopyToClipboard(entryInfo.username,
clipboardHelper?.timeoutCopyToClipboard(entry.username,
getString(R.string.copy_field, getString(R.string.copy_field,
getString(R.string.entry_user_name))) getString(R.string.entry_user_name)))
database.stopManageEntry(entry)
} }
val isFirstTimeAskAllowCopyPasswordAndProtectedFields = val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
@@ -277,11 +282,9 @@ class EntryActivity : LockingActivity() {
val onPasswordCopyClickListener: View.OnClickListener? = if (allowCopyPasswordAndProtectedFields) { val onPasswordCopyClickListener: View.OnClickListener? = if (allowCopyPasswordAndProtectedFields) {
View.OnClickListener { View.OnClickListener {
database.startManageEntry(entry) clipboardHelper?.timeoutCopyToClipboard(entryInfo.password,
clipboardHelper?.timeoutCopyToClipboard(entry.password,
getString(R.string.copy_field, getString(R.string.copy_field,
getString(R.string.entry_password))) getString(R.string.entry_password)))
database.stopManageEntry(entry)
} }
} else { } else {
// If dialog not already shown // If dialog not already shown
@@ -291,44 +294,46 @@ class EntryActivity : LockingActivity() {
null null
} }
} }
entryContentsView?.assignPassword(entry.password, entryContentsView?.assignPassword(entryInfo.password,
allowCopyPasswordAndProtectedFields, allowCopyPasswordAndProtectedFields,
onPasswordCopyClickListener) onPasswordCopyClickListener)
//Assign OTP field //Assign OTP field
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress, entry.getOtpElement()?.let { otpElement ->
View.OnClickListener { entryContentsView?.assignOtp(otpElement, entryProgress) {
entry.getOtpElement()?.let { otpElement -> clipboardHelper?.timeoutCopyToClipboard(
clipboardHelper?.timeoutCopyToClipboard( otpElement.token,
otpElement.token, getString(R.string.copy_field, getString(R.string.entry_otp))
getString(R.string.copy_field, getString(R.string.entry_otp)) )
) }
} }
})
entryContentsView?.assignURL(entry.url) entryContentsView?.assignURL(entryInfo.url)
entryContentsView?.assignNotes(entry.notes) entryContentsView?.assignNotes(entryInfo.notes)
// Assign custom fields // Assign custom fields
if (mDatabase?.allowEntryCustomFields() == true) { if (mDatabase?.allowEntryCustomFields() == true) {
entryContentsView?.clearExtraFields() entryContentsView?.clearExtraFields()
entry.getExtraFields().forEach { field -> entryInfo.customFields.forEach { field ->
val label = field.name val label = field.name
val value = field.protectedValue // OTP field is already managed in dedicated view
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields if (label != OtpEntryFields.OTP_TOKEN_FIELD) {
if (allowCopyProtectedField) { val value = field.protectedValue
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) { val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
clipboardHelper?.timeoutCopyToClipboard( if (allowCopyProtectedField) {
value.toString(), entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
getString(R.string.copy_field, label) clipboardHelper?.timeoutCopyToClipboard(
) value.toString(),
} getString(R.string.copy_field, label)
} else { )
// If dialog not already shown }
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
} else { } else {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null) // If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
} else {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
}
} }
} }
} }
@@ -336,24 +341,16 @@ class EntryActivity : LockingActivity() {
entryContentsView?.setHiddenProtectedValue(!mShowPassword) entryContentsView?.setHiddenProtectedValue(!mShowPassword)
// Manage attachments // Manage attachments
mDatabase?.binaryPool?.let { binaryPool -> entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
entryContentsView?.assignAttachments(entry.getAttachments(binaryPool).toSet(), StreamDirection.DOWNLOAD) { attachmentItem -> createDocument(this, attachmentItem.name)?.let { requestCode ->
createDocument(this, attachmentItem.name)?.let { requestCode -> mAttachmentsToDownload[requestCode] = attachmentItem
mAttachmentsToDownload[requestCode] = attachmentItem
}
} }
} }
// Assign dates // Assign dates
entryContentsView?.assignCreationDate(entry.creationTime) entryContentsView?.assignCreationDate(entryInfo.creationTime)
entryContentsView?.assignModificationDate(entry.lastModificationTime) entryContentsView?.assignModificationDate(entryInfo.modificationTime)
entryContentsView?.assignLastAccessDate(entry.lastAccessTime) entryContentsView?.setExpires(entryInfo.expires, entryInfo.expiryTime)
entryContentsView?.setExpires(entry.isCurrentlyExpires)
if (entry.expires) {
entryContentsView?.assignExpiresDate(entry.expiryTime)
} else {
entryContentsView?.assignExpiresDate(getString(R.string.never))
}
// Manage history // Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
@@ -368,8 +365,6 @@ class EntryActivity : LockingActivity() {
// Assign special data // Assign special data
entryContentsView?.assignUUID(entry.nodeId.id) entryContentsView?.assignUUID(entry.nodeId.id)
database.stopManageEntry(entry)
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -407,6 +402,9 @@ class EntryActivity : LockingActivity() {
menu.findItem(R.id.menu_save_database)?.isVisible = false menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false menu.findItem(R.id.menu_edit)?.isVisible = false
} }
if (mSpecialMode != SpecialMode.DEFAULT) {
menu.findItem(R.id.menu_reload_database)?.isVisible = false
}
val gotoUrl = menu.findItem(R.id.menu_goto_url) val gotoUrl = menu.findItem(R.id.menu_goto_url)
gotoUrl?.apply { gotoUrl?.apply {
@@ -500,6 +498,9 @@ class EntryActivity : LockingActivity() {
R.id.menu_save_database -> { R.id.menu_save_database -> {
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly) mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
} }
R.id.menu_reload_database -> {
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
}
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any) android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)

View File

@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.activities
import android.app.Activity import android.app.Activity
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.app.TimePickerDialog import android.app.TimePickerDialog
import android.app.assist.AssistStructure
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@@ -48,6 +47,8 @@ import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Compani
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
@@ -60,6 +61,7 @@ import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
@@ -134,7 +136,7 @@ class EntryEditActivity : LockingActivity(),
} }
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout) coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
stopService(Intent(this, ClipboardEntryNotificationService::class.java)) stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java)) stopService(Intent(this, KeyboardEntryNotificationService::class.java))
@@ -334,6 +336,10 @@ class EntryEditActivity : LockingActivity(),
Log.e(TAG, "Unable to retrieve entry after database action", e) Log.e(TAG, "Unable to retrieve entry after database action", e)
} }
} }
ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
finish()
}
} }
coordinatorLayout?.showActionError(result) coordinatorLayout?.showActionError(result)
} }
@@ -360,7 +366,7 @@ class EntryEditActivity : LockingActivity(),
// Build Autofill response with the entry selected // Build Autofill response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mDatabase?.let { database -> mDatabase?.let { database ->
AutofillHelper.buildResponse(this@EntryEditActivity, AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
entry.getEntryInfo(database)) entry.getEntryInfo(database))
} }
} }
@@ -478,8 +484,12 @@ class EntryEditActivity : LockingActivity(),
} }
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) { override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
verifyNameField(newField) { if (oldField.name.equals(newField.name, true)) {
entryEditFragment?.replaceExtraField(oldField, newField) entryEditFragment?.replaceExtraField(oldField, newField)
} else {
verifyNameField(newField) {
entryEditFragment?.replaceExtraField(oldField, newField)
}
} }
} }
@@ -609,13 +619,7 @@ class EntryEditActivity : LockingActivity(),
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
MenuUtil.contributionMenuInflater(menuInflater, menu)
val inflater = menuInflater
inflater.inflate(R.menu.database, menu)
// Save database not needed here
menu.findItem(R.id.menu_save_database)?.isVisible = false
MenuUtil.contributionMenuInflater(inflater, menu)
return true return true
} }
@@ -672,9 +676,6 @@ class EntryEditActivity : LockingActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_save_database -> {
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
}
R.id.menu_contribute -> { R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this) MenuUtil.onContributionItemSelected(this)
return true return true
@@ -908,7 +909,7 @@ class EntryEditActivity : LockingActivity(),
*/ */
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity, fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure, autofillComponent: AutofillComponent,
group: Group, group: Group,
searchInfo: SearchInfo? = null) { searchInfo: SearchInfo? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
@@ -916,7 +917,7 @@ class EntryEditActivity : LockingActivity(),
intent.putExtra(KEY_PARENT, group.nodeId) intent.putExtra(KEY_PARENT, group.nodeId)
AutofillHelper.startActivityForAutofillResult(activity, AutofillHelper.startActivityForAutofillResult(activity,
intent, intent,
assistStructure, autofillComponent,
searchInfo) searchInfo)
} }
} }

View File

@@ -37,6 +37,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.stylish.StylishFragment import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
@@ -148,6 +149,8 @@ class EntryEditFragment: StylishFragment() {
iconColor = taIconColor?.getColor(0, Color.WHITE) ?: Color.WHITE iconColor = taIconColor?.getColor(0, Color.WHITE) ?: Color.WHITE
taIconColor?.recycle() taIconColor?.recycle()
rootView?.resetAppTimeoutWhenViewFocusedOrChanged(requireContext())
// Retrieve the new entry after an orientation change // Retrieve the new entry after an orientation change
if (arguments?.containsKey(KEY_TEMP_ENTRY_INFO) == true) if (arguments?.containsKey(KEY_TEMP_ENTRY_INFO) == true)
mEntryInfo = arguments?.getParcelable(KEY_TEMP_ENTRY_INFO) ?: mEntryInfo mEntryInfo = arguments?.getParcelable(KEY_TEMP_ENTRY_INFO) ?: mEntryInfo

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.app.Activity import android.app.Activity
import android.app.assist.AssistStructure
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@@ -48,6 +47,7 @@ import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
@@ -434,8 +434,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
when (item.itemId) { when (item.itemId) {
android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url) android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
} }
MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
companion object { companion object {
@@ -501,11 +501,11 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity, fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure, autofillComponent: AutofillComponent,
searchInfo: SearchInfo? = null) { searchInfo: SearchInfo? = null) {
AutofillHelper.startActivityForAutofillResult(activity, AutofillHelper.startActivityForAutofillResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java), Intent(activity, FileDatabaseSelectActivity::class.java),
assistStructure, autofillComponent,
searchInfo) searchInfo)
} }

View File

@@ -20,7 +20,6 @@ package com.kunzisoft.keepass.activities
import android.app.Activity import android.app.Activity
import android.app.SearchManager import android.app.SearchManager
import android.app.assist.AssistStructure
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -50,7 +49,9 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
@@ -69,6 +70,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
@@ -153,7 +155,7 @@ class GroupActivity : LockingActivity(),
taTextColor.recycle() taTextColor.recycle()
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(rootContainerView) rootContainerView?.resetAppTimeoutWhenViewFocusedOrChanged(this)
// Retrieve elements after an orientation change // Retrieve elements after an orientation change
if (savedInstanceState != null) { if (savedInstanceState != null) {
@@ -227,10 +229,10 @@ class GroupActivity : LockingActivity(),
currentGroup, searchInfo) currentGroup, searchInfo)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
}, },
{ searchInfo, assistStructure -> { searchInfo, autofillComponent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
EntryEditActivity.launchForAutofillResult(this@GroupActivity, EntryEditActivity.launchForAutofillResult(this@GroupActivity,
assistStructure, autofillComponent,
currentGroup, searchInfo) currentGroup, searchInfo)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
} else { } else {
@@ -316,7 +318,12 @@ class GroupActivity : LockingActivity(),
if (result.isSuccess) { if (result.isSuccess) {
// Rebuild all the list to avoid bug when delete node from sort // Rebuild all the list to avoid bug when delete node from sort
mListNodesFragment?.rebuildList() try {
mListNodesFragment?.rebuildList()
} catch (e: Exception) {
Log.e(TAG, "Unable to rebuild the list after deletion")
e.printStackTrace()
}
// Add trash in views list if it doesn't exists // Add trash in views list if it doesn't exists
if (database.isRecycleBinEnabled) { if (database.isRecycleBinEnabled) {
@@ -336,6 +343,12 @@ class GroupActivity : LockingActivity(),
} }
} }
} }
ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
} }
coordinatorLayout?.showActionError(result) coordinatorLayout?.showActionError(result)
@@ -659,7 +672,7 @@ class GroupActivity : LockingActivity(),
// Build response with the entry selected // Build response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
mDatabase?.let { database -> mDatabase?.let { database ->
AutofillHelper.buildResponse(this, AutofillHelper.buildResponseAndSetResult(this,
entry.getEntryInfo(database)) entry.getEntryInfo(database))
} }
} }
@@ -872,6 +885,8 @@ class GroupActivity : LockingActivity(),
} }
if (mSpecialMode == SpecialMode.DEFAULT) { if (mSpecialMode == SpecialMode.DEFAULT) {
MenuUtil.defaultMenuInflater(inflater, menu) MenuUtil.defaultMenuInflater(inflater, menu)
} else {
menu.findItem(R.id.menu_reload_database)?.isVisible = false
} }
// Menu for recycle bin // Menu for recycle bin
@@ -997,6 +1012,10 @@ class GroupActivity : LockingActivity(),
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly) mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
return true return true
} }
R.id.menu_reload_database -> {
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
return true
}
R.id.menu_empty_recycle_bin -> { R.id.menu_empty_recycle_bin -> {
mCurrentGroup?.getChildren()?.let { listChildren -> mCurrentGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements // Automatically delete all elements
@@ -1124,7 +1143,16 @@ class GroupActivity : LockingActivity(),
private fun rebuildListNodes() { private fun rebuildListNodes() {
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment? mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment?
// to refresh fragment // to refresh fragment
mListNodesFragment?.rebuildList() try {
mListNodesFragment?.rebuildList()
} catch (e: Exception) {
e.printStackTrace()
coordinatorLayout?.let { coordinatorLayout ->
Snackbar.make(coordinatorLayout,
R.string.error_rebuild_list,
Snackbar.LENGTH_LONG).asError().show()
}
}
mCurrentGroup = mListNodesFragment?.mainGroup mCurrentGroup = mListNodesFragment?.mainGroup
// Remove search in intent // Remove search in intent
deletePreviousSearchGroup() deletePreviousSearchGroup()
@@ -1295,14 +1323,14 @@ class GroupActivity : LockingActivity(),
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity, fun launchForAutofillResult(activity: Activity,
readOnly: Boolean, readOnly: Boolean,
assistStructure: AssistStructure, autofillComponent: AutofillComponent,
searchInfo: SearchInfo? = null, searchInfo: SearchInfo? = null,
autoSearch: Boolean = false) { autoSearch: Boolean = false) {
checkTimeAndBuildIntent(activity, null, readOnly) { intent -> checkTimeAndBuildIntent(activity, null, readOnly) { intent ->
intent.putExtra(AUTO_SEARCH_KEY, autoSearch) intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
AutofillHelper.startActivityForAutofillResult(activity, AutofillHelper.startActivityForAutofillResult(activity,
intent, intent,
assistStructure, autofillComponent,
searchInfo) searchInfo)
} }
} }
@@ -1419,21 +1447,21 @@ class GroupActivity : LockingActivity(),
} }
) )
}, },
{ searchInfo, assistStructure -> { searchInfo, autofillComponent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SearchHelper.checkAutoSearchInfo(activity, SearchHelper.checkAutoSearchInfo(activity,
Database.getInstance(), Database.getInstance(),
searchInfo, searchInfo,
{ items -> { items ->
// Response is build // Response is build
AutofillHelper.buildResponse(activity, items) AutofillHelper.buildResponseAndSetResult(activity, items)
onValidateSpecialMode() onValidateSpecialMode()
}, },
{ {
// Here no search info found, disable auto search // Here no search info found, disable auto search
GroupActivity.launchForAutofillResult(activity, GroupActivity.launchForAutofillResult(activity,
readOnly, readOnly,
assistStructure, autofillComponent,
searchInfo, searchInfo,
false) false)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()

View File

@@ -22,30 +22,24 @@ package com.kunzisoft.keepass.activities
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.* import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener { class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
@@ -197,7 +191,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
// Refresh data // Refresh data
rebuildList() try {
rebuildList()
} catch (e: Exception) {
Log.e(TAG, "Unable to rebuild the list during resume")
e.printStackTrace()
}
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) { if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found " // To show the " no search entry found "
@@ -209,10 +208,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
} }
@Throws(IllegalArgumentException::class)
fun rebuildList() { fun rebuildList() {
// Add elements to the list // Add elements to the list
mainGroup?.let { mainGroup -> mainGroup?.let { mainGroup ->
mAdapter?.apply { mAdapter?.apply {
// Thrown an exception when sort cannot be performed
rebuildList(mainGroup) rebuildList(mainGroup)
// To visually change the elements // To visually change the elements
if (PreferencesUtil.APPEARANCE_CHANGED) { if (PreferencesUtil.APPEARANCE_CHANGED) {
@@ -231,8 +232,13 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
// Tell the adapter to refresh it's list // Tell the adapter to refresh it's list
mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters) try {
rebuildList() mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
rebuildList()
} catch (e:Exception) {
Log.e(TAG, "Unable to rebuild the list with the sort")
e.printStackTrace()
}
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.app.Activity import android.app.Activity
import android.app.assist.AssistStructure
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
@@ -37,8 +36,8 @@ import android.widget.*
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
@@ -49,9 +48,9 @@ import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
@@ -69,14 +68,13 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import kotlinx.android.synthetic.main.activity_password.* import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException import java.io.FileNotFoundException
open class PasswordActivity : SpecialModeActivity() { open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.BuilderListener {
// Views // Views
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
@@ -86,9 +84,8 @@ open class PasswordActivity : SpecialModeActivity() {
private var confirmButtonView: Button? = null private var confirmButtonView: Button? = null
private var checkboxPasswordView: CompoundButton? = null private var checkboxPasswordView: CompoundButton? = null
private var checkboxKeyFileView: CompoundButton? = null private var checkboxKeyFileView: CompoundButton? = null
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null private var advancedUnlockFragment: AdvancedUnlockFragment? = null
private var infoContainerView: ViewGroup? = null private var infoContainerView: ViewGroup? = null
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
private val databaseFileViewModel: DatabaseFileViewModel by viewModels() private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
@@ -114,7 +111,6 @@ open class PasswordActivity : SpecialModeActivity() {
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
private var mAllowAutoOpenBiometricPrompt: Boolean = true private var mAllowAutoOpenBiometricPrompt: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -134,7 +130,6 @@ open class PasswordActivity : SpecialModeActivity() {
keyFileSelectionView = findViewById(R.id.keyfile_selection) keyFileSelectionView = findViewById(R.id.keyfile_selection)
checkboxPasswordView = findViewById(R.id.password_checkbox) checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox) checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
advancedUnlockInfoView = findViewById(R.id.biometric_info)
infoContainerView = findViewById(R.id.activity_password_info_container) infoContainerView = findViewById(R.id.activity_password_info_container)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
@@ -161,10 +156,6 @@ open class PasswordActivity : SpecialModeActivity() {
} }
}) })
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
enableOrNotTheConfirmationButton()
}
// If is a view intent // If is a view intent
getUriFromIntent(intent) getUriFromIntent(intent)
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) { if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
@@ -174,6 +165,24 @@ open class PasswordActivity : SpecialModeActivity() {
mAllowAutoOpenBiometricPrompt = savedInstanceState.getBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT) mAllowAutoOpenBiometricPrompt = savedInstanceState.getBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT)
} }
// Init Biometric elements
advancedUnlockFragment = supportFragmentManager
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
if (advancedUnlockFragment == null) {
advancedUnlockFragment = AdvancedUnlockFragment()
supportFragmentManager.commit {
replace(R.id.fragment_advanced_unlock_container_view,
advancedUnlockFragment!!,
UNLOCK_FRAGMENT_TAG)
}
}
// Listen password checkbox to init advanced unlock and confirmation button
checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
advancedUnlockFragment?.checkUnlockAvailability()
enableOrNotTheConfirmationButton()
}
// Observe if default database // Observe if default database
databaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase -> databaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
mDefaultDatabase = isDefaultDatabase mDefaultDatabase = isDefaultDatabase
@@ -207,12 +216,7 @@ open class PasswordActivity : SpecialModeActivity() {
when (actionTask) { when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> { ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error // Recheck advanced unlock if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { advancedUnlockFragment?.initAdvancedUnlockMode()
if (PreferencesUtil.isAdvancedUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initAdvancedUnlockMode()
}
}
if (result.isSuccess) { if (result.isSuccess) {
mDatabaseKeyFileUri = null mDatabaseKeyFileUri = null
@@ -320,6 +324,33 @@ open class PasswordActivity : SpecialModeActivity() {
finish() finish()
} }
override fun retrieveCredentialForEncryption(): String {
return passwordView?.text?.toString() ?: ""
}
override fun conditionToStoreCredential(): Boolean {
return checkboxPasswordView?.isChecked == true
}
override fun onCredentialEncrypted(databaseUri: Uri,
encryptedCredential: String,
ivSpec: String) {
// Load the database if password is registered with biometric
verifyCheckboxesAndLoadDatabase(
CipherDatabaseEntity(
databaseUri.toString(),
encryptedCredential,
ivSpec)
)
}
override fun onCredentialDecrypted(databaseUri: Uri,
decryptedCredential: String) {
// Load the database if password is retrieve from biometric
// Retrieve from biometric
verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential)
}
private val onEditorActionListener = object : TextView.OnEditorActionListener { private val onEditorActionListener = object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == IME_ACTION_DONE) { if (actionId == IME_ACTION_DONE) {
@@ -386,48 +417,9 @@ open class PasswordActivity : SpecialModeActivity() {
verifyCheckboxesAndLoadDatabase(password, keyFileUri) verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else { } else {
// Init Biometric elements // Init Biometric elements
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { advancedUnlockFragment?.loadDatabase(databaseFileUri,
if (PreferencesUtil.isAdvancedUnlockEnable(this)) { mAllowAutoOpenBiometricPrompt
if (advancedUnlockedManager == null && mProgressDatabaseTaskProvider?.isBinded() != true)
&& databaseFileUri != null) {
advancedUnlockedManager = AdvancedUnlockedManager(this,
databaseFileUri,
advancedUnlockInfoView,
checkboxPasswordView,
enableButtonOnCheckedChangeListener,
passwordView,
{ passwordEncrypted, ivSpec ->
// Load the database if password is registered with biometric
if (passwordEncrypted != null && ivSpec != null) {
verifyCheckboxesAndLoadDatabase(
CipherDatabaseEntity(
databaseFileUri.toString(),
passwordEncrypted,
ivSpec)
)
}
},
{ passwordDecrypted ->
// Load the database if password is retrieve from biometric
passwordDecrypted?.let {
// Retrieve from biometric
verifyKeyFileCheckboxAndLoadDatabase(it)
}
})
}
advancedUnlockedManager?.isBiometricPromptAutoOpenEnable =
mAllowAutoOpenBiometricPrompt && mProgressDatabaseTaskProvider?.isBinded() != true
advancedUnlockedManager?.checkBiometricAvailability()
} else {
advancedUnlockInfoView?.visibility = View.GONE
advancedUnlockedManager?.destroy()
advancedUnlockedManager = null
}
}
if (advancedUnlockedManager == null) {
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
}
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
} }
enableOrNotTheConfirmationButton() enableOrNotTheConfirmationButton()
@@ -479,11 +471,6 @@ open class PasswordActivity : SpecialModeActivity() {
override fun onPause() { override fun onPause() {
mProgressDatabaseTaskProvider?.unregisterProgressTask() mProgressDatabaseTaskProvider?.unregisterProgressTask()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.destroy()
advancedUnlockedManager = null
}
// Reinit locking activity UI variable // Reinit locking activity UI variable
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true mAllowAutoOpenBiometricPrompt = true
@@ -592,11 +579,6 @@ open class PasswordActivity : SpecialModeActivity() {
MenuUtil.defaultMenuInflater(inflater, menu) MenuUtil.defaultMenuInflater(inflater, menu)
} }
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
}
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
launchEducation(menu) launchEducation(menu)
@@ -606,13 +588,13 @@ open class PasswordActivity : SpecialModeActivity() {
// Check permission // Check permission
private fun checkPermission() { private fun checkPermission() {
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE if (Build.VERSION.SDK_INT in 23..28
val permissions = arrayOf(writePermission)
if (Build.VERSION.SDK_INT >= 23
&& !readOnly && !readOnly
&& !mPermissionAsked) { && !mPermissionAsked) {
mPermissionAsked = true mPermissionAsked = true
// Check self permission to show or not the dialog // Check self permission to show or not the dialog
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissions = arrayOf(writePermission)
if (toolbar != null if (toolbar != null
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) { && ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST) ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
@@ -672,21 +654,14 @@ open class PasswordActivity : SpecialModeActivity() {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu)
}) })
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M advancedUnlockFragment?.performEducation(passwordActivityEducation,
&& !readOnlyEducationPerformed) { readOnlyEducationPerformed,
val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(this) {
PreferencesUtil.isAdvancedUnlockEnable(applicationContext) performedNextEducation(passwordActivityEducation, menu)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) },
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.visibility == View.VISIBLE {
&& advancedUnlockInfoView?.unlockIconImageView != null performedNextEducation(passwordActivityEducation, menu)
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!, })
{
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
})
}
} }
} }
@@ -708,10 +683,7 @@ open class PasswordActivity : SpecialModeActivity() {
readOnly = !readOnly readOnly = !readOnly
changeOpenFileReadIcon(item) changeOpenFileReadIcon(item)
} }
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
advancedUnlockedManager?.deleteEncryptedDatabaseKey()
}
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
@@ -725,6 +697,9 @@ open class PasswordActivity : SpecialModeActivity() {
mAllowAutoOpenBiometricPrompt = false mAllowAutoOpenBiometricPrompt = false
// To get device credential unlock result
advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data)
// To get entry in result // To get entry in result
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
@@ -745,7 +720,7 @@ open class PasswordActivity : SpecialModeActivity() {
when (resultCode) { when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK -> { LockingActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews() clearCredentialsViews()
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this)) Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
} }
Activity.RESULT_CANCELED -> { Activity.RESULT_CANCELED -> {
clearCredentialsViews() clearCredentialsViews()
@@ -758,6 +733,8 @@ open class PasswordActivity : SpecialModeActivity() {
private val TAG = PasswordActivity::class.java.name private val TAG = PasswordActivity::class.java.name
private const val UNLOCK_FRAGMENT_TAG = "UNLOCK_FRAGMENT_TAG"
private const val KEY_FILENAME = "fileName" private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile" private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW" private const val VIEW_INTENT = "android.intent.action.VIEW"
@@ -861,13 +838,13 @@ open class PasswordActivity : SpecialModeActivity() {
fun launchForAutofillResult(activity: Activity, fun launchForAutofillResult(activity: Activity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?, keyFile: Uri?,
assistStructure: AssistStructure, autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) { searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
AutofillHelper.startActivityForAutofillResult( AutofillHelper.startActivityForAutofillResult(
activity, activity,
intent, intent,
assistStructure, autofillComponent,
searchInfo) searchInfo)
} }
} }
@@ -925,11 +902,11 @@ open class PasswordActivity : SpecialModeActivity() {
searchInfo) searchInfo)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
}, },
{ searchInfo, assistStructure -> // Autofill Selection Action { searchInfo, autofillComponent -> // Autofill Selection Action
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PasswordActivity.launchForAutofillResult(activity, PasswordActivity.launchForAutofillResult(activity,
databaseUri, keyFile, databaseUri, keyFile,
assistStructure, autofillComponent,
searchInfo) searchInfo)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
} else { } else {

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
class DatabaseChangedDialogFragment : DialogFragment() {
var actionDatabaseListener: ActionDatabaseChangedListener? = null
override fun onPause() {
super.onPause()
actionDatabaseListener = null
this.dismiss()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val oldSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(OLD_FILE_DATABASE_INFO)
val newSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(NEW_FILE_DATABASE_INFO)
if (oldSnapFileDatabaseInfo != null && newSnapFileDatabaseInfo != null) {
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
val stringBuilder = SpannableStringBuilder()
if (newSnapFileDatabaseInfo.exists) {
stringBuilder.append(getString(R.string.warning_database_info_changed))
stringBuilder.append("\n\n" +oldSnapFileDatabaseInfo.toString(activity)
+ "\n\n" +
newSnapFileDatabaseInfo.toString(activity) + "\n\n")
stringBuilder.append(getString(R.string.warning_database_info_changed_options))
} else {
stringBuilder.append(getString(R.string.warning_database_revoked))
}
builder.setMessage(stringBuilder)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
actionDatabaseListener?.validateDatabaseChanged()
}
return builder.create()
}
}
return super.onCreateDialog(savedInstanceState)
}
interface ActionDatabaseChangedListener {
fun validateDatabaseChanged()
}
companion object {
const val DATABASE_CHANGED_DIALOG_TAG = "databaseChangedDialogFragment"
private const val OLD_FILE_DATABASE_INFO = "OLD_FILE_DATABASE_INFO"
private const val NEW_FILE_DATABASE_INFO = "NEW_FILE_DATABASE_INFO"
fun getInstance(oldSnapFileDatabaseInfo: SnapFileDatabaseInfo,
newSnapFileDatabaseInfo: SnapFileDatabaseInfo)
: DatabaseChangedDialogFragment {
val fragment = DatabaseChangedDialogFragment()
fragment.arguments = Bundle().apply {
putParcelable(OLD_FILE_DATABASE_INFO, oldSnapFileDatabaseInfo)
putParcelable(NEW_FILE_DATABASE_INFO, newSnapFileDatabaseInfo)
}
return fragment
}
}
}

View File

@@ -19,10 +19,10 @@
*/ */
package com.kunzisoft.keepass.activities.helpers package com.kunzisoft.keepass.activities.helpers
import android.app.assist.AssistStructure
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
@@ -106,7 +106,7 @@ object EntrySelectionHelper {
fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode { fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (AutofillHelper.retrieveAssistStructure(intent) != null) if (AutofillHelper.retrieveAutofillComponent(intent) != null)
return SpecialMode.SELECTION return SpecialMode.SELECTION
} }
return intent.getSerializableExtra(KEY_SPECIAL_MODE) as SpecialMode? return intent.getSerializableExtra(KEY_SPECIAL_MODE) as SpecialMode?
@@ -119,7 +119,7 @@ object EntrySelectionHelper {
fun retrieveTypeModeFromIntent(intent: Intent): TypeMode { fun retrieveTypeModeFromIntent(intent: Intent): TypeMode {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (AutofillHelper.retrieveAssistStructure(intent) != null) if (AutofillHelper.retrieveAutofillComponent(intent) != null)
return TypeMode.AUTOFILL return TypeMode.AUTOFILL
} }
return intent.getSerializableExtra(KEY_TYPE_MODE) as TypeMode? ?: TypeMode.DEFAULT return intent.getSerializableExtra(KEY_TYPE_MODE) as TypeMode? ?: TypeMode.DEFAULT
@@ -136,7 +136,7 @@ object EntrySelectionHelper {
saveAction: (searchInfo: SearchInfo) -> Unit, saveAction: (searchInfo: SearchInfo) -> Unit,
keyboardSelectionAction: (searchInfo: SearchInfo?) -> Unit, keyboardSelectionAction: (searchInfo: SearchInfo?) -> Unit,
autofillSelectionAction: (searchInfo: SearchInfo?, autofillSelectionAction: (searchInfo: SearchInfo?,
assistStructure: AssistStructure) -> Unit, autofillComponent: AutofillComponent) -> Unit,
autofillRegistrationAction: (registerInfo: RegisterInfo?) -> Unit) { autofillRegistrationAction: (registerInfo: RegisterInfo?) -> Unit) {
when (retrieveSpecialModeFromIntent(intent)) { when (retrieveSpecialModeFromIntent(intent)) {
@@ -167,14 +167,14 @@ object EntrySelectionHelper {
} }
SpecialMode.SELECTION -> { SpecialMode.SELECTION -> {
val searchInfo: SearchInfo? = retrieveSearchInfoFromIntent(intent) val searchInfo: SearchInfo? = retrieveSearchInfoFromIntent(intent)
var assistStructureInit = false var autofillComponentInit = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.retrieveAssistStructure(intent)?.let { assistStructure -> AutofillHelper.retrieveAutofillComponent(intent)?.let { autofillComponent ->
autofillSelectionAction.invoke(searchInfo, assistStructure) autofillSelectionAction.invoke(searchInfo, autofillComponent)
assistStructureInit = true autofillComponentInit = true
} }
} }
if (!assistStructureInit) { if (!autofillComponentInit) {
if (intent.getSerializableExtra(KEY_SPECIAL_MODE) != null) { if (intent.getSerializableExtra(KEY_SPECIAL_MODE) != null) {
when (retrieveTypeModeFromIntent(intent)) { when (retrieveTypeModeFromIntent(intent)) {
TypeMode.DEFAULT -> { TypeMode.DEFAULT -> {

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.activities.lock package com.kunzisoft.keepass.activities.lock
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent import android.view.MotionEvent
@@ -163,35 +164,6 @@ abstract class LockingActivity : SpecialModeActivity() {
sendBroadcast(Intent(LOCK_ACTION)) sendBroadcast(Intent(LOCK_ACTION))
} }
/**
* To reset the app timeout when a view is focused or changed
*/
@SuppressLint("ClickableViewAccessibility")
protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View?) {
views.forEach {
it?.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Log.d(TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
}
}
false
}
it?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
// Log.d(TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
}
}
if (it is ViewGroup) {
for (i in 0..it.childCount) {
resetAppTimeoutWhenViewFocusedOrChanged(it.getChildAt(i))
}
}
}
}
override fun onBackPressed() { override fun onBackPressed() {
if (mTimeoutEnable) { if (mTimeoutEnable) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) { TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
@@ -204,7 +176,7 @@ abstract class LockingActivity : SpecialModeActivity() {
companion object { companion object {
private const val TAG = "LockingActivity" const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450 const val RESULT_EXIT_LOCK = 1450
@@ -215,3 +187,28 @@ abstract class LockingActivity : SpecialModeActivity() {
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
} }
} }
/**
* To reset the app timeout when a view is focused or changed
*/
@SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context) {
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
//Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context)
}
}
false
}
setOnFocusChangeListener { _, _ ->
//Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context)
}
}
}

View File

@@ -18,7 +18,7 @@ import com.kunzisoft.keepass.view.SpecialModeView
abstract class SpecialModeActivity : StylishActivity() { abstract class SpecialModeActivity : StylishActivity() {
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
protected var mTypeMode: TypeMode = TypeMode.DEFAULT private var mTypeMode: TypeMode = TypeMode.DEFAULT
private var mSpecialModeView: SpecialModeView? = null private var mSpecialModeView: SpecialModeView? = null

View File

@@ -34,7 +34,7 @@ class App : MultiDexApplication() {
} }
override fun onTerminate() { override fun onTerminate() {
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this)) Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
super.onTerminate() super.onTerminate()
} }
} }

View File

@@ -47,7 +47,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri), UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""), fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""),
fileDatabaseInfo.exists, fileDatabaseInfo.exists,
fileDatabaseInfo.getModificationString(), fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString() fileDatabaseInfo.getSizeString()
) )
}, },
@@ -90,7 +90,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri), UriUtil.decode(fileDatabaseHistoryEntity.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias), fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
fileDatabaseInfo.exists, fileDatabaseInfo.exists,
fileDatabaseInfo.getModificationString(), fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString() fileDatabaseInfo.getSizeString()
) )
) )
@@ -152,7 +152,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
UriUtil.decode(fileDatabaseHistory.databaseUri), UriUtil.decode(fileDatabaseHistory.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias), fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists, fileDatabaseInfo.exists,
fileDatabaseInfo.getModificationString(), fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString() fileDatabaseInfo.getSizeString()
) )
} }

View File

@@ -0,0 +1,7 @@
package com.kunzisoft.keepass.autofill
import android.app.assist.AssistStructure
import android.view.inputmethod.InlineSuggestionsRequest
data class AutofillComponent(val assistStructure: AssistStructure,
val inlineSuggestionsRequest: InlineSuggestionsRequest?)

View File

@@ -19,18 +19,27 @@
*/ */
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.PendingIntent
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import android.service.autofill.Dataset import android.service.autofill.Dataset
import android.service.autofill.FillResponse import android.service.autofill.FillResponse
import android.service.autofill.InlinePresentation
import android.util.Log import android.util.Log
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue import android.view.autofill.AutofillValue
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
@@ -38,8 +47,11 @@ import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.icons.createIconFromDatabaseIcon
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
@@ -47,11 +59,17 @@ object AutofillHelper {
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165 private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
private const val ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE private const val EXTRA_ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST"
fun retrieveAssistStructure(intent: Intent?): AssistStructure? { fun retrieveAutofillComponent(intent: Intent?): AutofillComponent? {
intent?.let { intent?.getParcelableExtra<AssistStructure?>(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure ->
return it.getParcelableExtra(ASSIST_STRUCTURE) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AutofillComponent(assistStructure,
intent.getParcelableExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST))
} else {
AutofillComponent(assistStructure, null)
}
} }
return null return null
} }
@@ -68,26 +86,10 @@ object AutofillHelper {
return "" return ""
} }
internal fun addHeader(responseBuilder: FillResponse.Builder, private fun buildDataset(context: Context,
packageName: String,
webDomain: String?,
applicationId: String?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (webDomain != null) {
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_web_domain).apply {
setTextViewText(R.id.autofill_web_domain_text, webDomain)
})
} else if (applicationId != null) {
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_app_id).apply {
setTextViewText(R.id.autofill_app_id_text, applicationId)
})
}
}
}
internal fun buildDataset(context: Context,
entryInfo: EntryInfo, entryInfo: EntryInfo,
struct: StructureParser.Result): Dataset? { struct: StructureParser.Result,
inlinePresentation: InlinePresentation?): Dataset? {
val title = makeEntryTitle(entryInfo) val title = makeEntryTitle(entryInfo)
val views = newRemoteViews(context, title, entryInfo.icon) val views = newRemoteViews(context, title, entryInfo.icon)
val builder = Dataset.Builder(views) val builder = Dataset.Builder(views)
@@ -100,6 +102,12 @@ object AutofillHelper {
builder.setValue(password, AutofillValue.forText(entryInfo.password)) builder.setValue(password, AutofillValue.forText(entryInfo.password))
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlinePresentation?.let {
builder.setInlinePresentation(it)
}
}
return try { return try {
builder.build() builder.build()
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
@@ -108,44 +116,120 @@ object AutofillHelper {
} }
} }
@RequiresApi(Build.VERSION_CODES.R)
@SuppressLint("RestrictedApi")
private fun buildInlinePresentationForEntry(context: Context,
inlineSuggestionsRequest: InlineSuggestionsRequest,
positionItem: Int,
entryInfo: EntryInfo): InlinePresentation? {
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
val maxSuggestion = inlineSuggestionsRequest.maxSuggestionCount
if (positionItem <= maxSuggestion-1
&& inlinePresentationSpecs.size > positionItem) {
val inlinePresentationSpec = inlinePresentationSpecs[positionItem]
// Make sure that the IME spec claims support for v1 UI template.
val imeStyle = inlinePresentationSpec.style
if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
return null
// Build the content for IME UI
val pendingIntent = PendingIntent.getActivity(context,
0,
Intent(context, AutofillSettingsActivity::class.java),
0)
return InlinePresentation(
InlineSuggestionUi.newContentBuilder(pendingIntent).apply {
setContentDescription(context.getString(R.string.autofill_sign_in_prompt))
setTitle(entryInfo.title)
setSubtitle(entryInfo.username)
setStartIcon(Icon.createWithResource(context, R.mipmap.ic_launcher_round).apply {
setTintBlendMode(BlendMode.DST)
})
buildIconFromEntry(context, entryInfo)?.let { icon ->
setEndIcon(icon.apply {
setTintBlendMode(BlendMode.DST)
})
}
}.build().slice, inlinePresentationSpec, false)
}
return null
}
fun buildResponse(context: Context,
entriesInfo: List<EntryInfo>,
parseResult: StructureParser.Result,
inlineSuggestionsRequest: InlineSuggestionsRequest?): FillResponse {
val responseBuilder = FillResponse.Builder()
// Add Header
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val packageName = context.packageName
parseResult.webDomain?.let { webDomain ->
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_web_domain).apply {
setTextViewText(R.id.autofill_web_domain_text, webDomain)
})
} ?: kotlin.run {
parseResult.applicationId?.let { applicationId ->
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_app_id).apply {
setTextViewText(R.id.autofill_app_id_text, applicationId)
})
}
}
}
// Add inline suggestion for new IME and dataset
entriesInfo.forEachIndexed { index, entryInfo ->
val inlinePresentation = inlineSuggestionsRequest?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
buildInlinePresentationForEntry(context, inlineSuggestionsRequest, index, entryInfo)
} else {
null
}
}
responseBuilder.addDataset(buildDataset(context, entryInfo, parseResult, inlinePresentation))
}
return responseBuilder.build()
}
/** /**
* Build the Autofill response for one entry * Build the Autofill response for one entry
*/ */
fun buildResponse(activity: Activity, entryInfo: EntryInfo) { fun buildResponseAndSetResult(activity: Activity, entryInfo: EntryInfo) {
buildResponse(activity, ArrayList<EntryInfo>().apply { add(entryInfo) }) buildResponseAndSetResult(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
} }
/** /**
* Build the Autofill response for many entry * Build the Autofill response for many entry
*/ */
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) { fun buildResponseAndSetResult(activity: Activity, entriesInfo: List<EntryInfo>) {
if (entriesInfo.isEmpty()) { if (entriesInfo.isEmpty()) {
activity.setResult(Activity.RESULT_CANCELED) activity.setResult(Activity.RESULT_CANCELED)
} else { } else {
var setResultOk = false var setResultOk = false
activity.intent?.extras?.let { extras -> activity.intent?.getParcelableExtra<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { structure ->
if (extras.containsKey(ASSIST_STRUCTURE)) { StructureParser(structure).parse()?.let { result ->
activity.intent?.getParcelableExtra<AssistStructure>(ASSIST_STRUCTURE)?.let { structure -> // New Response
StructureParser(structure).parse()?.let { result -> val response = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// New Response val inlineSuggestionsRequest = activity.intent?.getParcelableExtra<InlineSuggestionsRequest?>(EXTRA_INLINE_SUGGESTIONS_REQUEST)
val responseBuilder = FillResponse.Builder() if (inlineSuggestionsRequest != null) {
entriesInfo.forEach { Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show()
responseBuilder.addDataset(buildDataset(activity, it, result))
}
val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.")
mReplyIntent.putExtra(
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
responseBuilder.build())
setResultOk = true
activity.setResult(Activity.RESULT_OK, mReplyIntent)
} }
buildResponse(activity, entriesInfo, result, inlineSuggestionsRequest)
} else {
buildResponse(activity, entriesInfo, result, null)
} }
val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.")
mReplyIntent.putExtra(
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
response)
setResultOk = true
activity.setResult(Activity.RESULT_OK, mReplyIntent)
} }
if (!setResultOk) { }
Log.w(activity.javaClass.name, "Failed Autofill auth.") if (!setResultOk) {
activity.setResult(Activity.RESULT_CANCELED) Log.w(activity.javaClass.name, "Failed Autofill auth.")
} activity.setResult(Activity.RESULT_CANCELED)
} }
} }
} }
@@ -155,10 +239,16 @@ object AutofillHelper {
*/ */
fun startActivityForAutofillResult(activity: Activity, fun startActivityForAutofillResult(activity: Activity,
intent: Intent, intent: Intent,
assistStructure: AssistStructure, autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) { searchInfo: SearchInfo?) {
EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.SELECTION) EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.SELECTION)
intent.putExtra(ASSIST_STRUCTURE, assistStructure) intent.putExtra(EXTRA_ASSIST_STRUCTURE, autofillComponent.assistStructure)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& PreferencesUtil.isAutofillInlineSuggestionsEnable(activity)) {
autofillComponent.inlineSuggestionsRequest?.let {
intent.putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
}
}
EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo) EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo)
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE) activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
} }
@@ -192,4 +282,11 @@ object AutofillHelper {
} }
return presentation return presentation
} }
private fun buildIconFromEntry(context: Context, entryInfo: EntryInfo): Icon? {
return createIconFromDatabaseIcon(context,
Database.getInstance().drawFactory,
entryInfo.icon,
ContextCompat.getColor(context, R.color.green))
}
} }

View File

@@ -19,37 +19,50 @@
*/ */
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
import android.app.PendingIntent
import android.content.Intent
import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import android.os.CancellationSignal import android.os.CancellationSignal
import android.service.autofill.* import android.service.autofill.*
import android.util.Log import android.util.Log
import android.view.autofill.AutofillId import android.view.autofill.AutofillId
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.v1.InlineSuggestionUi
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AutofillLauncherActivity import com.kunzisoft.keepass.activities.AutofillLauncherActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class KeeAutofillService : AutofillService() { class KeeAutofillService : AutofillService() {
var applicationIdBlocklist: Set<String>? = null var applicationIdBlocklist: Set<String>? = null
var webDomainBlocklist: Set<String>? = null var webDomainBlocklist: Set<String>? = null
var askToSaveData: Boolean = false var askToSaveData: Boolean = false
var autofillInlineSuggestionsEnabled: Boolean = false
private var mLock = AtomicBoolean() private var mLock = AtomicBoolean()
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
getPreferences()
}
private fun getPreferences() {
applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this) applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this)
webDomainBlocklist = PreferencesUtil.webDomainBlocklist(this) webDomainBlocklist = PreferencesUtil.webDomainBlocklist(this)
askToSaveData = PreferencesUtil.askToSaveAutofillData(this) // TODO apply when changed askToSaveData = PreferencesUtil.askToSaveAutofillData(this)
autofillInlineSuggestionsEnabled = PreferencesUtil.isAutofillInlineSuggestionsEnable(this)
} }
override fun onFillRequest(request: FillRequest, override fun onFillRequest(request: FillRequest,
@@ -75,7 +88,16 @@ class KeeAutofillService : AutofillService() {
} }
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain -> SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain ->
searchInfo.webDomain = webDomainWithoutSubDomain searchInfo.webDomain = webDomainWithoutSubDomain
launchSelection(searchInfo, parseResult, callback) val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& autofillInlineSuggestionsEnabled) {
request.inlineSuggestionsRequest
} else {
null
}
launchSelection(searchInfo,
parseResult,
inlineSuggestionsRequest,
callback)
} }
} }
} }
@@ -84,39 +106,40 @@ class KeeAutofillService : AutofillService() {
private fun launchSelection(searchInfo: SearchInfo, private fun launchSelection(searchInfo: SearchInfo,
parseResult: StructureParser.Result, parseResult: StructureParser.Result,
inlineSuggestionsRequest: InlineSuggestionsRequest?,
callback: FillCallback) { callback: FillCallback) {
SearchHelper.checkAutoSearchInfo(this, SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(), Database.getInstance(),
searchInfo, searchInfo,
{ items -> { items ->
val responseBuilder = FillResponse.Builder() callback.onSuccess(
AutofillHelper.addHeader(responseBuilder, packageName, AutofillHelper.buildResponse(this,
parseResult.webDomain, parseResult.applicationId) items, parseResult, inlineSuggestionsRequest)
items.forEach { )
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
}
callback.onSuccess(responseBuilder.build())
}, },
{ {
// Show UI if no search result // Show UI if no search result
showUIForEntrySelection(parseResult, searchInfo, callback) showUIForEntrySelection(parseResult,
searchInfo, inlineSuggestionsRequest, callback)
}, },
{ {
// Show UI if database not open // Show UI if database not open
showUIForEntrySelection(parseResult, searchInfo, callback) showUIForEntrySelection(parseResult,
searchInfo, inlineSuggestionsRequest, callback)
} }
) )
} }
private fun showUIForEntrySelection(parseResult: StructureParser.Result, private fun showUIForEntrySelection(parseResult: StructureParser.Result,
searchInfo: SearchInfo, searchInfo: SearchInfo,
inlineSuggestionsRequest: InlineSuggestionsRequest?,
callback: FillCallback) { callback: FillCallback) {
parseResult.allAutofillIds().let { autofillIds -> parseResult.allAutofillIds().let { autofillIds ->
if (autofillIds.isNotEmpty()) { if (autofillIds.isNotEmpty()) {
// If the entire Autofill Response is authenticated, AuthActivity is used // If the entire Autofill Response is authenticated, AuthActivity is used
// to generate Response. // to generate Response.
val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(this, val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(this,
searchInfo) searchInfo, inlineSuggestionsRequest)
val responseBuilder = FillResponse.Builder() val responseBuilder = FillResponse.Builder()
val remoteViewsUnlock: RemoteViews = if (!parseResult.webDomain.isNullOrEmpty()) { val remoteViewsUnlock: RemoteViews = if (!parseResult.webDomain.isNullOrEmpty()) {
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply { RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
@@ -149,7 +172,40 @@ class KeeAutofillService : AutofillService() {
) )
} }
} }
// Build response
// Build inline presentation
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& autofillInlineSuggestionsEnabled) {
var inlinePresentation: InlinePresentation? = null
inlineSuggestionsRequest?.let {
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
if (inlineSuggestionsRequest.maxSuggestionCount > 0
&& inlinePresentationSpecs.size > 0) {
val inlinePresentationSpec = inlinePresentationSpecs[0]
// Make sure that the IME spec claims support for v1 UI template.
val imeStyle = inlinePresentationSpec.style
if (UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1)) {
// Build the content for IME UI
inlinePresentation = InlinePresentation(
InlineSuggestionUi.newContentBuilder(
PendingIntent.getActivity(this,
0,
Intent(this, AutofillSettingsActivity::class.java),
0)
).apply {
setContentDescription(getString(R.string.autofill_sign_in_prompt))
setTitle(getString(R.string.autofill_sign_in_prompt))
setStartIcon(Icon.createWithResource(this@KeeAutofillService, R.mipmap.ic_launcher_round).apply {
setTintBlendMode(BlendMode.DST)
})
}.build().slice, inlinePresentationSpec, false)
}
}
}
// Build response
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation)
}
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock) responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
callback.onSuccess(responseBuilder.build()) callback.onSuccess(responseBuilder.build())
} }
@@ -190,6 +246,7 @@ class KeeAutofillService : AutofillService() {
override fun onConnected() { override fun onConnected() {
Log.d(TAG, "onConnected") Log.d(TAG, "onConnected")
getPreferences()
} }
override fun onDisconnected() { override fun onDisconnected() {

View File

@@ -33,7 +33,7 @@ import java.util.*
* Parse AssistStructure and guess username and password fields. * Parse AssistStructure and guess username and password fields.
*/ */
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
internal class StructureParser(private val structure: AssistStructure) { class StructureParser(private val structure: AssistStructure) {
private var result: Result? = null private var result: Result? = null
private var usernameNeeded = true private var usernameNeeded = true
@@ -274,7 +274,7 @@ internal class StructureParser(private val structure: AssistStructure) {
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
internal class Result { class Result {
var applicationId: String? = null var applicationId: String? = null
var webDomain: String? = null var webDomain: String? = null

View File

@@ -0,0 +1,10 @@
package com.kunzisoft.keepass.biometric
import androidx.annotation.StringRes
import javax.crypto.Cipher
data class AdvancedUnlockCryptoPrompt(var cipher: Cipher,
@StringRes var promptTitleId: Int,
@StringRes var promptDescriptionId: Int? = null,
var isDeviceCredentialOperation: Boolean,
var isBiometricOperation: Boolean)

View File

@@ -0,0 +1,628 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.*
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import com.getkeepsafe.taptargetview.TapTargetView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.exception.IODatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
private var mBuilderListener: BuilderListener? = null
private var mAdvancedUnlockEnabled = false
private var mAutoOpenPromptEnabled = false
private var advancedUnlockManager: AdvancedUnlockManager? = null
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE
private var mAdvancedUnlockInfoView: AdvancedUnlockInfoView? = null
var databaseFileUri: Uri? = null
private set
/**
* Manage setting to auto open biometric prompt
*/
private var mAutoOpenPrompt: Boolean = false
get() {
return field && mAutoOpenPromptEnabled
}
// Variable to check if the prompt can be open (if the right activity is currently shown)
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
private var allowOpenBiometricPrompt = false
private lateinit var cipherDatabaseAction : CipherDatabaseAction
private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null
// Only to fix multiple fingerprint menu #332
private var mAllowAdvancedUnlockMenu = false
private var mAddBiometricMenuInProgress = false
// Only keep connection when we request a device credential activity
private var keepConnection = false
override fun onAttach(context: Context) {
super.onAttach(context)
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context)
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBuilderListener = context as BuilderListener
}
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + BuilderListener::class.java.name)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
setHasOptionsMenu(true)
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_advanced_unlock, container, false)
mAdvancedUnlockInfoView = rootView.findViewById(R.id.advanced_unlock_view)
return rootView
}
private data class ActivityResult(var requestCode: Int, var resultCode: Int, var data: Intent?)
private var activityResult: ActivityResult? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// To wait resume
if (keepConnection) {
activityResult = ActivityResult(requestCode, resultCode, data)
}
keepConnection = false
}
override fun onResume() {
super.onResume()
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext())
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(requireContext())
keepConnection = false
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
if (mAllowAdvancedUnlockMenu)
inflater.inflate(R.menu.advanced_unlock, menu)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEncryptedDatabaseKey()
}
}
return super.onOptionsItemSelected(item)
}
fun loadDatabase(databaseUri: Uri?, autoOpenPrompt: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// To get device credential unlock result, only if same database uri
if (databaseUri != null
&& mAdvancedUnlockEnabled) {
activityResult?.let {
if (databaseUri == databaseFileUri) {
advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode)
} else {
disconnect()
}
} ?: run {
connect(databaseUri)
this.mAutoOpenPrompt = autoOpenPrompt
}
} else {
disconnect()
}
activityResult = null
}
}
/**
* Check unlock availability and change the current mode depending of device's state
*/
fun checkUnlockAvailability() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
allowOpenBiometricPrompt = true
if (PreferencesUtil.isBiometricUnlockEnable(requireContext())) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
// biometric not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
} else {
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
} else {
selectMode()
}
}
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(requireContext())) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
if (AdvancedUnlockManager.isDeviceSecure(requireContext())) {
selectMode()
} else {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun selectMode() {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
advancedUnlockManager = AdvancedUnlockManager { requireActivity() }
// callback for fingerprint findings
advancedUnlockManager?.advancedUnlockCallback = this
}
// Recheck to change the mode
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else {
if (mBuilderListener?.conditionToStoreCredential() == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode(if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun toggleMode(newBiometricMode: Mode) {
if (newBiometricMode != biometricMode) {
biometricMode = newBiometricMode
initAdvancedUnlockMode()
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initNotAvailable() {
showViews(false)
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
}
@RequiresApi(Build.VERSION_CODES.M)
private fun openBiometricSetting() {
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
requireContext().startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initSecurityUpdateRequired() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
openBiometricSetting()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initNotConfigured() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("")
openBiometricSetting()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initKeyManagerNotAvailable() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
openBiometricSetting()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initWaitData() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("")
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
requireContext().getString(R.string.credential_before_click_advanced_unlock_button))
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
activity?.runOnUiThread {
if (allowOpenBiometricPrompt) {
if (cryptoPrompt.isDeviceCredentialOperation)
keepConnection = true
try {
advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt)
} catch (e: Exception) {
Log.e(TAG, "Unable to open advanced unlock prompt", e)
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initEncryptData() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential)
setAdvancedUnlockedMessageView("")
advancedUnlockManager?.initEncryptData { cryptoPrompt ->
// Set listener to open the biometric dialog and save credential
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
openAdvancedUnlockPrompt(cryptoPrompt)
}
} ?: throw Exception("AdvancedUnlockManager not initialized")
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initDecryptData() {
showViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_unlock_database)
setAdvancedUnlockedMessageView("")
advancedUnlockManager?.let { unlockHelper ->
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
cipherDatabase?.let {
unlockHelper.initDecryptData(it.specParameters) { cryptoPrompt ->
// Set listener to open the biometric dialog and check credential
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
openAdvancedUnlockPrompt(cryptoPrompt)
}
// Auto open the biometric prompt
if (mAutoOpenPrompt) {
mAutoOpenPrompt = false
openAdvancedUnlockPrompt(cryptoPrompt)
}
}
} ?: deleteEncryptedDatabaseKey()
}
} ?: throw IODatabaseException()
} ?: throw Exception("AdvancedUnlockManager not initialized")
}
@Synchronized
fun initAdvancedUnlockMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mAllowAdvancedUnlockMenu = false
try {
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
}
} catch (e: Exception) {
onGenericException(e)
}
invalidateBiometricMenu()
}
}
private fun invalidateBiometricMenu() {
// Show fingerprint key deletion
if (!mAddBiometricMenuInProgress) {
mAddBiometricMenuInProgress = true
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
mAllowAdvancedUnlockMenu = containsCipher
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
mAddBiometricMenuInProgress = false
activity?.invalidateOptionsMenu()
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
fun connect(databaseUri: Uri) {
showViews(true)
this.databaseFileUri = databaseUri
cipherDatabaseListener = object: CipherDatabaseAction.DatabaseListener {
override fun onDatabaseCleared() {
deleteEncryptedDatabaseKey()
}
}
cipherDatabaseAction.apply {
reloadPreferences()
cipherDatabaseListener?.let {
registerDatabaseListener(it)
}
}
checkUnlockAvailability()
}
@RequiresApi(Build.VERSION_CODES.M)
fun disconnect(hideViews: Boolean = true,
closePrompt: Boolean = true) {
this.databaseFileUri = null
// Close the biometric prompt
allowOpenBiometricPrompt = false
if (closePrompt)
advancedUnlockManager?.closeBiometricPrompt()
cipherDatabaseListener?.let {
cipherDatabaseAction.unregisterDatabaseListener(it)
}
biometricMode = Mode.BIOMETRIC_UNAVAILABLE
if (hideViews) {
showViews(false)
}
}
@RequiresApi(Build.VERSION_CODES.M)
fun deleteEncryptedDatabaseKey() {
allowOpenBiometricPrompt = false
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
advancedUnlockManager?.closeBiometricPrompt()
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
checkUnlockAvailability()
}
}
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
activity?.runOnUiThread {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedMessageView(errString.toString())
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationFailed() {
activity?.runOnUiThread {
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationSucceeded() {
activity?.runOnUiThread {
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> {
}
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
}
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> {
}
Mode.KEY_MANAGER_UNAVAILABLE -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
advancedUnlockManager?.encryptData(credential)
}
AdvancedUnlockNotificationService.startServiceForTimeout(requireContext())
}
Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
cipherDatabase?.encryptedValue?.let { value ->
advancedUnlockManager?.decryptData(value)
} ?: deleteEncryptedDatabaseKey()
}
} ?: run {
onAuthenticationError(-1, getString(R.string.error_database_uri_null))
}
}
}
}
}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec)
}
}
override fun handleDecryptedResult(decryptedValue: String) {
// Load database directly with password retrieve
databaseFileUri?.let {
mBuilderListener?.onCredentialDecrypted(it, decryptedValue)
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onInvalidKeyException(e: Exception) {
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
}
override fun onGenericException(e: Exception) {
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
setAdvancedUnlockedMessageView(errorMessage)
}
private fun showViews(show: Boolean) {
activity?.runOnUiThread {
mAdvancedUnlockInfoView?.visibility = if (show)
View.VISIBLE
else {
View.GONE
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedTitleView(textId: Int) {
activity?.runOnUiThread {
mAdvancedUnlockInfoView?.setTitle(textId)
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedMessageView(textId: Int) {
activity?.runOnUiThread {
mAdvancedUnlockInfoView?.setMessage(textId)
}
}
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
activity?.runOnUiThread {
mAdvancedUnlockInfoView?.message = text
}
}
fun performEducation(passwordActivityEducation: PasswordActivityEducation,
readOnlyEducationPerformed: Boolean,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
onEducationViewClick,
onOuterViewClick)
}
} catch (ignored: Exception) {}
}
enum class Mode {
BIOMETRIC_UNAVAILABLE,
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED,
KEY_MANAGER_UNAVAILABLE,
WAIT_CREDENTIAL,
STORE_CREDENTIAL,
EXTRACT_CREDENTIAL
}
interface BuilderListener {
fun retrieveCredentialForEncryption(): String
fun conditionToStoreCredential(): Boolean
fun onCredentialEncrypted(databaseUri: Uri, encryptedCredential: String, ivSpec: String)
fun onCredentialDecrypted(databaseUri: Uri, decryptedCredential: String)
}
override fun onPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!keepConnection) {
// If close prompt, bug "user not authenticated in Android R"
disconnect(false)
advancedUnlockManager = null
}
}
super.onPause()
}
override fun onDestroyView() {
mAdvancedUnlockInfoView = null
super.onDestroyView()
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
disconnect()
advancedUnlockManager = null
mBuilderListener = null
}
super.onDestroy()
}
override fun onDetach() {
mBuilderListener = null
super.onDetach()
}
companion object {
private val TAG = AdvancedUnlockFragment::class.java.name
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2020 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -19,6 +19,7 @@
*/ */
package com.kunzisoft.keepass.biometric package com.kunzisoft.keepass.biometric
import android.app.Activity
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
@@ -31,6 +32,7 @@ import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.* import androidx.biometric.BiometricManager.Authenticators.*
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -44,48 +46,74 @@ import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity) {
private var biometricPrompt: BiometricPrompt? = null
private var keyStore: KeyStore? = null private var keyStore: KeyStore? = null
private var keyGenerator: KeyGenerator? = null private var keyGenerator: KeyGenerator? = null
private var cipher: Cipher? = null private var cipher: Cipher? = null
private var keyguardManager: KeyguardManager? = null
private var cryptoObject: BiometricPrompt.CryptoObject? = null private var biometricPrompt: BiometricPrompt? = null
private var authenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
advancedUnlockCallback?.onAuthenticationSucceeded()
}
override fun onAuthenticationFailed() {
advancedUnlockCallback?.onAuthenticationFailed()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
advancedUnlockCallback?.onAuthenticationError(errorCode, errString)
}
}
var advancedUnlockCallback: AdvancedUnlockCallback? = null
private var isKeyManagerInit = false private var isKeyManagerInit = false
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
var biometricUnlockCallback: BiometricUnlockCallback? = null
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(context) private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(retrieveContext())
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext())
val isKeyManagerInitialized: Boolean val isKeyManagerInitialized: Boolean
get() { get() {
if (!isKeyManagerInit) { if (!isKeyManagerInit) {
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized")) advancedUnlockCallback?.onGenericException(Exception("Biometric not initialized"))
} }
return isKeyManagerInit 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 { init {
if (allowInitKeyStore(context)) { if (isDeviceSecure(retrieveContext())
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager? && (biometricUnlockEnable || deviceCredentialUnlockEnable)) {
try { try {
this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE) this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE)
this.keyGenerator = KeyGenerator.getInstance(BIOMETRIC_KEY_ALGORITHM, BIOMETRIC_KEYSTORE) this.keyGenerator = KeyGenerator.getInstance(ADVANCED_UNLOCK_KEY_ALGORITHM, ADVANCED_UNLOCK_KEYSTORE)
this.cipher = Cipher.getInstance( this.cipher = Cipher.getInstance(
BIOMETRIC_KEY_ALGORITHM + "/" ADVANCED_UNLOCK_KEY_ALGORITHM + "/"
+ BIOMETRIC_BLOCKS_MODES + "/" + ADVANCED_UNLOCK_BLOCKS_MODES + "/"
+ BIOMETRIC_ENCRYPTION_PADDING) + ADVANCED_UNLOCK_ENCRYPTION_PADDING)
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
isKeyManagerInit = (keyStore != null isKeyManagerInit = (keyStore != null
&& keyGenerator != null && keyGenerator != null
&& cipher != null) && cipher != null)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to initialize the keystore", e) Log.e(TAG, "Unable to initialize the keystore", e)
isKeyManagerInit = false isKeyManagerInit = false
biometricUnlockCallback?.onBiometricException(e) advancedUnlockCallback?.onGenericException(e)
} }
} else { } else {
// really not much to do when no fingerprint support found // really not much to do when no fingerprint support found
@@ -103,22 +131,20 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
keyStore.load(null) keyStore.load(null)
try { try {
if (!keyStore.containsAlias(BIOMETRIC_KEYSTORE_KEY)) { if (!keyStore.containsAlias(ADVANCED_UNLOCK_KEYSTORE_KEY)) {
// Set the alias of the entry in Android KeyStore where the key will appear // Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder // and the constrains (purposes) in the constructor of the Builder
keyGenerator?.init( keyGenerator?.init(
KeyGenParameterSpec.Builder( KeyGenParameterSpec.Builder(
BIOMETRIC_KEYSTORE_KEY, ADVANCED_UNLOCK_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
// Require the user to authenticate with a fingerprint to authorize every use // Require the user to authenticate with a fingerprint to authorize every use
// of the key // of the key, don't use it for device credential because it's the user authentication
.setUserAuthenticationRequired(true)
.apply { .apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R if (biometricUnlockEnable) {
&& deviceCredentialUnlockEnable) { setUserAuthenticationRequired(true)
setUserAuthenticationParameters(0, KeyProperties.AUTH_DEVICE_CREDENTIAL)
} }
} }
.build()) .build())
@@ -126,56 +152,46 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to create a key in keystore", e) Log.e(TAG, "Unable to create a key in keystore", e)
biometricUnlockCallback?.onBiometricException(e) advancedUnlockCallback?.onGenericException(e)
} }
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey? return keyStore.getKey(ADVANCED_UNLOCK_KEYSTORE_KEY, null) as SecretKey?
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to retrieve the key in keystore", e) Log.e(TAG, "Unable to retrieve the key in keystore", e)
biometricUnlockCallback?.onBiometricException(e) advancedUnlockCallback?.onGenericException(e)
} }
return null return null
} }
fun initEncryptData(actionIfCypherInit fun initEncryptData(actionIfCypherInit
: (biometricPrompt: BiometricPrompt?, : (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
if (!isKeyManagerInitialized) { if (!isKeyManagerInitialized) {
return return
} }
try { try {
// TODO if (keyguardManager?.isDeviceSecure == true) {
getSecretKey()?.let { secretKey -> getSecretKey()?.let { secretKey ->
cipher?.init(Cipher.ENCRYPT_MODE, secretKey) cipher?.let { cipher ->
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
initBiometricPrompt() actionIfCypherInit.invoke(
AdvancedUnlockCryptoPrompt(
val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply { cipher,
setTitle(context.getString(R.string.advanced_unlock_prompt_store_credential_title)) R.string.advanced_unlock_prompt_store_credential_title,
setDescription(context.getString(R.string.advanced_unlock_prompt_store_credential_message)) R.string.advanced_unlock_prompt_store_credential_message,
setConfirmationRequired(true) isDeviceCredentialOperation(), isBiometricOperation())
if (deviceCredentialUnlockEnable) { )
setAllowedAuthenticators(DEVICE_CREDENTIAL) }
} else {
setNegativeButtonText(context.getString(android.R.string.cancel))
}
}.build()
actionIfCypherInit.invoke(biometricPrompt,
cryptoObject,
promptInfoStoreCredential)
} }
} catch (unrecoverableKeyException: UnrecoverableKeyException) { } catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException) Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException) advancedUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) { } catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException) Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException) advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to initialize encrypt data", e) Log.e(TAG, "Unable to initialize encrypt data", e)
biometricUnlockCallback?.onBiometricException(e) advancedUnlockCallback?.onGenericException(e)
} }
} }
@@ -190,57 +206,46 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
// passes updated iv spec on to callback so this can be stored for decryption // passes updated iv spec on to callback so this can be stored for decryption
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec -> cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP) val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue) advancedUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
} }
} catch (e: Exception) { } catch (e: Exception) {
val exception = Exception(context.getString(R.string.keystore_not_accessible), e)
Log.e(TAG, "Unable to encrypt data", e) Log.e(TAG, "Unable to encrypt data", e)
biometricUnlockCallback?.onBiometricException(exception) advancedUnlockCallback?.onGenericException(e)
} }
} }
fun initDecryptData(ivSpecValue: String, actionIfCypherInit fun initDecryptData(ivSpecValue: String, actionIfCypherInit
: (biometricPrompt: BiometricPrompt?, : (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
if (!isKeyManagerInitialized) { if (!isKeyManagerInitialized) {
return return
} }
try { try {
// TODO if (keyguardManager?.isDeviceSecure == true) {
// important to restore spec here that was used for decryption // important to restore spec here that was used for decryption
val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP) val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP)
val spec = IvParameterSpec(iv) val spec = IvParameterSpec(iv)
getSecretKey()?.let { secretKey -> getSecretKey()?.let { secretKey ->
cipher?.init(Cipher.DECRYPT_MODE, secretKey, spec) cipher?.let { cipher ->
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
initBiometricPrompt() actionIfCypherInit.invoke(
AdvancedUnlockCryptoPrompt(
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply { cipher,
setTitle(context.getString(R.string.advanced_unlock_prompt_extract_credential_title)) R.string.advanced_unlock_prompt_extract_credential_title,
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message)) null,
setConfirmationRequired(false) isDeviceCredentialOperation(), isBiometricOperation())
if (deviceCredentialUnlockEnable) { )
setAllowedAuthenticators(DEVICE_CREDENTIAL) }
} else {
setNegativeButtonText(context.getString(android.R.string.cancel))
}
}.build()
actionIfCypherInit.invoke(biometricPrompt,
cryptoObject,
promptInfoExtractCredential)
} }
} catch (unrecoverableKeyException: UnrecoverableKeyException) { } catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException) Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
deleteKeystoreKey() deleteKeystoreKey()
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) { } catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException) Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException) advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to initialize decrypt data", e) Log.e(TAG, "Unable to initialize decrypt data", e)
biometricUnlockCallback?.onBiometricException(e) advancedUnlockCallback?.onGenericException(e)
} }
} }
@@ -252,33 +257,73 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
// actual decryption here // actual decryption here
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP) val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
cipher?.doFinal(encrypted)?.let { decrypted -> cipher?.doFinal(encrypted)?.let { decrypted ->
biometricUnlockCallback?.handleDecryptedResult(String(decrypted)) advancedUnlockCallback?.handleDecryptedResult(String(decrypted))
} }
} catch (badPaddingException: BadPaddingException) { } catch (badPaddingException: BadPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException) Log.e(TAG, "Unable to decrypt data", badPaddingException)
biometricUnlockCallback?.onInvalidKeyException(badPaddingException) advancedUnlockCallback?.onInvalidKeyException(badPaddingException)
} catch (e: Exception) { } catch (e: Exception) {
val exception = Exception(context.getString(R.string.keystore_not_accessible), e) Log.e(TAG, "Unable to decrypt data", e)
Log.e(TAG, "Unable to decrypt data", exception) advancedUnlockCallback?.onGenericException(e)
biometricUnlockCallback?.onBiometricException(exception)
} }
} }
fun deleteKeystoreKey() { fun deleteKeystoreKey() {
try { try {
keyStore?.load(null) keyStore?.load(null)
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY) keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to delete entry key in keystore", e) Log.e(TAG, "Unable to delete entry key in keystore", e)
biometricUnlockCallback?.onBiometricException(e) advancedUnlockCallback?.onGenericException(e)
}
}
@Suppress("DEPRECATION")
@Synchronized
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
// Init advanced unlock prompt
if (biometricPrompt == null) {
biometricPrompt = BiometricPrompt(retrieveContext(),
Executors.newSingleThreadExecutor(),
authenticationCallback)
}
val promptTitle = retrieveContext().getString(cryptoPrompt.promptTitleId)
val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId ->
retrieveContext().getString(descriptionId)
} ?: ""
if (cryptoPrompt.isBiometricOperation) {
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(promptTitle)
if (promptDescription.isNotEmpty())
setDescription(promptDescription)
setConfirmationRequired(false)
if (isDeviceCredentialBiometricOperation()) {
setAllowedAuthenticators(DEVICE_CREDENTIAL)
} else {
setNegativeButtonText(retrieveContext().getString(android.R.string.cancel))
}
}.build()
biometricPrompt?.authenticate(
promptInfoExtractCredential,
BiometricPrompt.CryptoObject(cryptoPrompt.cipher))
}
else if (cryptoPrompt.isDeviceCredentialOperation) {
val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java)
retrieveContext().startActivityForResult(
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription),
REQUEST_DEVICE_CREDENTIAL)
} }
} }
@Synchronized @Synchronized
fun initBiometricPrompt() { fun onActivityResult(requestCode: Int, resultCode: Int) {
if (biometricPrompt == null) { if (requestCode == REQUEST_DEVICE_CREDENTIAL) {
authenticationCallback?.let { if (resultCode == Activity.RESULT_OK) {
biometricPrompt = BiometricPrompt(context, Executors.newSingleThreadExecutor(), it) advancedUnlockCallback?.onAuthenticationSucceeded()
} else {
advancedUnlockCallback?.onAuthenticationFailed()
} }
} }
} }
@@ -287,25 +332,30 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
biometricPrompt?.cancelAuthentication() biometricPrompt?.cancelAuthentication()
} }
interface BiometricUnlockErrorCallback { interface AdvancedUnlockErrorCallback {
fun onInvalidKeyException(e: Exception) fun onInvalidKeyException(e: Exception)
fun onBiometricException(e: Exception) fun onGenericException(e: Exception)
} }
interface BiometricUnlockCallback : BiometricUnlockErrorCallback { interface AdvancedUnlockCallback : AdvancedUnlockErrorCallback {
fun onAuthenticationSucceeded()
fun onAuthenticationFailed()
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
fun handleEncryptedResult(encryptedValue: String, ivSpec: String) fun handleEncryptedResult(encryptedValue: String, ivSpec: String)
fun handleDecryptedResult(decryptedValue: String) fun handleDecryptedResult(decryptedValue: String)
} }
companion object { companion object {
private val TAG = BiometricUnlockDatabaseHelper::class.java.name private val TAG = AdvancedUnlockManager::class.java.name
private const val BIOMETRIC_KEYSTORE = "AndroidKeyStore" private const val ADVANCED_UNLOCK_KEYSTORE = "AndroidKeyStore"
private const val BIOMETRIC_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key" private const val ADVANCED_UNLOCK_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
private const val BIOMETRIC_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES private const val ADVANCED_UNLOCK_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
private const val BIOMETRIC_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
private const val BIOMETRIC_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7 private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
private const val REQUEST_DEVICE_CREDENTIAL = 556
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
fun canAuthenticate(context: Context): Int { fun canAuthenticate(context: Context): Int {
@@ -337,11 +387,9 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
} }
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
fun allowInitKeyStore(context: Context): Boolean { fun isDeviceSecure(context: Context): Boolean {
val biometricCanAuthenticate = canAuthenticate(context) val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
return ( biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS return keyguardManager?.isDeviceSecure ?: false
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
)
} }
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
@@ -365,36 +413,48 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
) )
} }
@RequiresApi(api = Build.VERSION_CODES.R) @RequiresApi(api = Build.VERSION_CODES.M)
fun deviceCredentialUnlockSupported(context: Context): Boolean { fun deviceCredentialUnlockSupported(context: Context): Boolean {
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
) || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ContextCompat.getSystemService(context, KeyguardManager::class.java)?.apply {
return isDeviceSecure
}
}
return false
} }
/** /**
* Remove entry key in keystore * Remove entry key in keystore
*/ */
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity, fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity,
biometricCallback: BiometricUnlockErrorCallback) { advancedCallback: AdvancedUnlockErrorCallback) {
BiometricUnlockDatabaseHelper(context).apply { AdvancedUnlockManager{ fragmentActivity }.apply {
biometricUnlockCallback = object : BiometricUnlockCallback { advancedUnlockCallback = object : AdvancedUnlockCallback {
override fun onAuthenticationSucceeded() {}
override fun onAuthenticationFailed() {}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {} override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleDecryptedResult(decryptedValue: String) {} override fun handleDecryptedResult(decryptedValue: String) {}
override fun onInvalidKeyException(e: Exception) { override fun onInvalidKeyException(e: Exception) {
biometricCallback.onInvalidKeyException(e) advancedCallback.onInvalidKeyException(e)
} }
override fun onBiometricException(e: Exception) { override fun onGenericException(e: Exception) {
biometricCallback.onBiometricException(e) advancedCallback.onGenericException(e)
} }
} }
deleteKeystoreKey() deleteKeystoreKey()

View File

@@ -1,422 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.widget.CompoundButton
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
@RequiresApi(api = Build.VERSION_CODES.M)
class AdvancedUnlockedManager(var context: FragmentActivity,
var databaseFileUri: Uri,
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
private var checkboxPasswordView: CompoundButton?,
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
var passwordView: TextView?,
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE
// Only to fix multiple fingerprint menu #332
private var mAllowAdvancedUnlockMenu = false
private var mAddBiometricMenuInProgress = false
/**
* Manage setting to auto open biometric prompt
*/
private var biometricPromptAutoOpenPreference = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
var isBiometricPromptAutoOpenEnable: Boolean = false
get() {
return field && biometricPromptAutoOpenPreference
}
// Variable to check if the prompt can be open (if the right activity is currently shown)
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
private var allowOpenBiometricPrompt = false
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
private val cipherDatabaseListener = object: CipherDatabaseAction.DatabaseListener {
override fun onDatabaseCleared() {
deleteEncryptedDatabaseKey()
}
}
init {
// Add a check listener to change fingerprint mode
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
checkBiometricAvailability()
// Add old listener to enable the button, only be call here because of onCheckedChange bug
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
}
cipherDatabaseAction.apply {
reloadPreferences()
registerDatabaseListener(cipherDatabaseListener)
}
}
/**
* Check biometric availability and change the current mode depending of device's state
*/
fun checkBiometricAvailability() {
if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
advancedUnlockInfoView?.setIconResource(R.drawable.bolt)
} else if (PreferencesUtil.isBiometricUnlockEnable(context)) {
advancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
}
// biometric not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(context)
allowOpenBiometricPrompt = true
if (!PreferencesUtil.isAdvancedUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED){
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
} else {
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
} else {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
}
// Recheck to change the mode
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else {
if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode(if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
}
}
}
}
}
private fun toggleMode(newBiometricMode: Mode) {
if (newBiometricMode != biometricMode) {
biometricMode = newBiometricMode
initAdvancedUnlockMode()
}
}
private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence) {
context.runOnUiThread {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
setAdvancedUnlockedMessageView(errString.toString())
}
}
override fun onAuthenticationFailed() {
context.runOnUiThread {
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
}
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
context.runOnUiThread {
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> {
}
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
}
Mode.BIOMETRIC_NOT_CONFIGURED -> {
}
Mode.KEY_MANAGER_UNAVAILABLE -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
AdvancedUnlockNotificationService.startServiceForTimeout(context)
}
Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences
cipherDatabaseAction.getCipherDatabase(databaseFileUri) { cipherDatabase ->
cipherDatabase?.encryptedValue?.let { value ->
biometricUnlockDatabaseHelper?.decryptData(value)
} ?: deleteEncryptedDatabaseKey()
}
}
}
}
}
}
private fun initNotAvailable() {
showFingerPrintViews(false)
advancedUnlockInfoView?.setIconViewClickListener(false, null)
}
@Suppress("DEPRECATION")
private fun openBiometricSetting() {
advancedUnlockInfoView?.setIconViewClickListener(false) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
context.startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
private fun initSecurityUpdateRequired() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
openBiometricSetting()
}
private fun initNotConfigured() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("")
openBiometricSetting()
}
private fun initKeyManagerNotAvailable() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
openBiometricSetting()
}
private fun initWaitData() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
biometricAuthenticationCallback.onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
context.getString(R.string.credential_before_click_advanced_unlock_button))
}
}
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo) {
context.runOnUiThread {
if (allowOpenBiometricPrompt) {
if (biometricPrompt != null) {
if (cryptoObject != null) {
biometricPrompt.authenticate(promptInfo, cryptoObject)
} else {
setAdvancedUnlockedTitleView(R.string.crypto_object_not_initialized)
}
} else {
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
}
}
}
}
private fun initEncryptData() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential)
setAdvancedUnlockedMessageView("")
biometricUnlockDatabaseHelper?.initEncryptData { biometricPrompt, cryptoObject, promptInfo ->
// Set listener to open the biometric dialog and save credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
}
}
}
private fun initDecryptData() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_unlock_database)
setAdvancedUnlockedMessageView("")
if (biometricUnlockDatabaseHelper != null) {
cipherDatabaseAction.getCipherDatabase(databaseFileUri) { cipherDatabase ->
cipherDatabase?.let {
biometricUnlockDatabaseHelper?.initDecryptData(it.specParameters) { biometricPrompt, cryptoObject, promptInfo ->
// Set listener to open the biometric dialog and check credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
}
// Auto open the biometric prompt
if (isBiometricPromptAutoOpenEnable) {
isBiometricPromptAutoOpenEnable = false
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
}
}
} ?: deleteEncryptedDatabaseKey()
}
}
}
@Synchronized
fun initAdvancedUnlockMode() {
mAllowAdvancedUnlockMenu = false
when (biometricMode) {
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
}
invalidateBiometricMenu()
}
private fun invalidateBiometricMenu() {
// Show fingerprint key deletion
if (!mAddBiometricMenuInProgress) {
mAddBiometricMenuInProgress = true
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
mAllowAdvancedUnlockMenu = containsCipher
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
mAddBiometricMenuInProgress = false
context.invalidateOptionsMenu()
}
}
}
fun destroy() {
// Close the biometric prompt
allowOpenBiometricPrompt = false
biometricUnlockDatabaseHelper?.closeBiometricPrompt()
// Restore the checked listener
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
cipherDatabaseAction.unregisterDatabaseListener(cipherDatabaseListener)
}
fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) {
if (mAllowAdvancedUnlockMenu)
menuInflater.inflate(R.menu.advanced_unlock, menu)
}
fun deleteEncryptedDatabaseKey() {
allowOpenBiometricPrompt = false
advancedUnlockInfoView?.setIconViewClickListener(false, null)
biometricUnlockDatabaseHelper?.closeBiometricPrompt()
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri) {
checkBiometricAvailability()
}
}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec)
}
override fun handleDecryptedResult(decryptedValue: String) {
// Load database directly with password retrieve
loadDatabaseAfterRetrieveCredentials.invoke(decryptedValue)
}
override fun onInvalidKeyException(e: Exception) {
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
}
override fun onBiometricException(e: Exception) {
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
setAdvancedUnlockedMessageView(errorMessage)
}
private fun showFingerPrintViews(show: Boolean) {
context.runOnUiThread {
advancedUnlockInfoView?.visibility = if (show) View.VISIBLE else View.GONE
}
}
private fun setAdvancedUnlockedTitleView(textId: Int) {
context.runOnUiThread {
advancedUnlockInfoView?.setTitle(textId)
}
}
private fun setAdvancedUnlockedMessageView(textId: Int) {
context.runOnUiThread {
advancedUnlockInfoView?.setMessage(textId)
}
}
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
context.runOnUiThread {
advancedUnlockInfoView?.message = text
}
}
enum class Mode {
BIOMETRIC_UNAVAILABLE,
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_NOT_CONFIGURED,
KEY_MANAGER_UNAVAILABLE,
WAIT_CREDENTIAL,
STORE_CREDENTIAL,
EXTRACT_CREDENTIAL
}
companion object {
private val TAG = AdvancedUnlockedManager::class.java.name
}
}

View File

@@ -29,11 +29,12 @@ object StreamCipherFactory {
private val SALSA_IV = byteArrayOf(0xE8.toByte(), 0x30, 0x09, 0x4B, 0x97.toByte(), 0x20, 0x5D, 0x2A) private val SALSA_IV = byteArrayOf(0xE8.toByte(), 0x30, 0x09, 0x4B, 0x97.toByte(), 0x20, 0x5D, 0x2A)
fun getInstance(alg: CrsAlgorithm?, key: ByteArray): StreamCipher? { @Throws(Exception::class)
fun getInstance(alg: CrsAlgorithm?, key: ByteArray): StreamCipher {
return when { return when {
alg === CrsAlgorithm.Salsa20 -> getSalsa20(key) alg === CrsAlgorithm.Salsa20 -> getSalsa20(key)
alg === CrsAlgorithm.ChaCha20 -> getChaCha20(key) alg === CrsAlgorithm.ChaCha20 -> getChaCha20(key)
else -> null else -> throw Exception("Invalid random cipher")
} }
} }

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.StringRes
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.stream.bytes16ToUuid import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
@@ -27,7 +28,11 @@ import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
class Argon2Kdf internal constructor() : KdfEngine() { class Argon2Kdf(private val type: Type) : KdfEngine() {
init {
uuid = type.CIPHER_UUID
}
override val defaultParameters: KdfParameters override val defaultParameters: KdfParameters
get() { get() {
@@ -45,12 +50,8 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val defaultKeyRounds: Long override val defaultKeyRounds: Long
get() = DEFAULT_ITERATIONS get() = DEFAULT_ITERATIONS
init {
uuid = CIPHER_UUID
}
override fun getName(resources: Resources): String { override fun getName(resources: Resources): String {
return resources.getString(R.string.kdf_Argon2) return resources.getString(type.nameId)
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -72,7 +73,9 @@ class Argon2Kdf internal constructor() : KdfEngine() {
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY) val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA) val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
return Argon2Native.transformKey(masterKey, return Argon2Native.transformKey(
type,
masterKey,
salt, salt,
parallelism, parallelism,
memory, memory,
@@ -141,9 +144,8 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxParallelism: Long override val maxParallelism: Long
get() = MAX_PARALLELISM get() = MAX_PARALLELISM
companion object { enum class Type(val CIPHER_UUID: UUID, @StringRes val nameId: Int) {
ARGON2_D(bytes16ToUuid(
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xEF.toByte(), byteArrayOf(0xEF.toByte(),
0x63.toByte(), 0x63.toByte(),
0x6D.toByte(), 0x6D.toByte(),
@@ -159,7 +161,27 @@ class Argon2Kdf internal constructor() : KdfEngine() {
0x03.toByte(), 0x03.toByte(),
0xE3.toByte(), 0xE3.toByte(),
0x0A.toByte(), 0x0A.toByte(),
0x0C.toByte())) 0x0C.toByte())), R.string.kdf_Argon2d),
ARGON2_ID(bytes16ToUuid(
byteArrayOf(0x9E.toByte(),
0x29.toByte(),
0x8B.toByte(),
0x19.toByte(),
0x56.toByte(),
0xDB.toByte(),
0x47.toByte(),
0x73.toByte(),
0xB2.toByte(),
0x3D.toByte(),
0xFC.toByte(),
0x3E.toByte(),
0xC6.toByte(),
0xF0.toByte(),
0xA1.toByte(),
0xE6.toByte())), R.string.kdf_Argon2id);
}
companion object {
private const val PARAM_SALT = "S" // byte[] private const val PARAM_SALT = "S" // byte[]
private const val PARAM_PARALLELISM = "P" // UInt32 private const val PARAM_PARALLELISM = "P" // UInt32

View File

@@ -26,12 +26,29 @@ import java.io.IOException;
public class Argon2Native { public class Argon2Native {
public static byte[] transformKey(byte[] password, byte[] salt, UnsignedInt parallelism, enum CType {
ARGON2_D(0),
ARGON2_I(1),
ARGON2_ID(2);
int cValue = 0;
CType(int i) {
cValue = i;
}
}
public static byte[] transformKey(Argon2Kdf.Type type, byte[] password, byte[] salt, UnsignedInt parallelism,
UnsignedInt memory, UnsignedInt iterations, byte[] secretKey, UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
byte[] associatedData, UnsignedInt version) throws IOException { byte[] associatedData, UnsignedInt version) throws IOException {
NativeLib.INSTANCE.init(); NativeLib.INSTANCE.init();
CType cType = CType.ARGON2_D;
if (type.equals(Argon2Kdf.Type.ARGON2_ID))
cType = CType.ARGON2_ID;
return nTransformMasterKey( return nTransformMasterKey(
cType.cValue,
password, password,
salt, salt,
parallelism.toKotlinInt(), parallelism.toKotlinInt(),
@@ -42,7 +59,7 @@ public class Argon2Native {
version.toKotlinInt()); version.toKotlinInt());
} }
private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism, private static native byte[] nTransformMasterKey(int type, byte[] password, byte[] salt, int parallelism,
int memory, int iterations, byte[] secretKey, int memory, int iterations, byte[] secretKey,
byte[] associatedData, int version) throws IOException; byte[] associatedData, int version) throws IOException;
} }

View File

@@ -21,5 +21,6 @@ package com.kunzisoft.keepass.crypto.keyDerivation
object KdfFactory { object KdfFactory {
var aesKdf = AesKdf() var aesKdf = AesKdf()
var argon2Kdf = Argon2Kdf() var argon2dKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_D)
var argon2idKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_ID)
} }

View File

@@ -26,7 +26,6 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.closeDatabase
class CreateDatabaseRunnable(context: Context, class CreateDatabaseRunnable(context: Context,
private val mDatabase: Database, private val mDatabase: Database,
@@ -47,7 +46,7 @@ class CreateDatabaseRunnable(context: Context,
createData(mDatabaseUri, databaseName, rootName) createData(mDatabaseUri, databaseName, rootName)
} }
} catch (e: Exception) { } catch (e: Exception) {
mDatabase.closeAndClear(UriUtil.getBinaryDir(context)) mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
setError(e) setError(e)
} }

View File

@@ -31,7 +31,6 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.closeDatabase
class LoadDatabaseRunnable(private val context: Context, class LoadDatabaseRunnable(private val context: Context,
private val mDatabase: Database, private val mDatabase: Database,
@@ -47,7 +46,7 @@ class LoadDatabaseRunnable(private val context: Context,
override fun onStartRun() { override fun onStartRun() {
// Clear before we load // Clear before we load
mDatabase.closeAndClear(UriUtil.getBinaryDir(context)) mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
} }
override fun onActionRun() { override fun onActionRun() {
@@ -59,9 +58,6 @@ class LoadDatabaseRunnable(private val context: Context,
mFixDuplicateUUID, mFixDuplicateUUID,
progressTaskUpdater) progressTaskUpdater)
} }
catch (e: DuplicateUuidDatabaseException) {
setError(e)
}
catch (e: LoadDatabaseException) { catch (e: LoadDatabaseException) {
setError(e) setError(e)
} }
@@ -83,7 +79,7 @@ class LoadDatabaseRunnable(private val context: Context,
// Register the current time to init the lock timer // Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context) PreferencesUtil.saveCurrentTime(context)
} else { } else {
mDatabase.closeAndClear(UriUtil.getBinaryDir(context)) mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
} }
} }

View File

@@ -26,6 +26,8 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
@@ -35,6 +37,7 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
@@ -44,6 +47,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
@@ -84,6 +88,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
private var serviceConnection: ServiceConnection? = null private var serviceConnection: ServiceConnection? = null
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener { private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) { override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
@@ -101,6 +106,28 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
} }
} }
private val mActionDatabaseListener = object: DatabaseChangedDialogFragment.ActionDatabaseChangedListener {
override fun validateDatabaseChanged() {
mBinder?.getService()?.saveDatabaseInfo()
}
}
private var databaseInfoListener = object: DatabaseTaskNotificationService.DatabaseInfoListener {
override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
newDatabaseInfo: SnapFileDatabaseInfo) {
if (databaseChangedDialogFragment == null) {
databaseChangedDialogFragment = activity.supportFragmentManager
.findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
}
if (progressTaskDialogFragment == null) {
databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(previousDatabaseInfo, newDatabaseInfo)
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
databaseChangedDialogFragment?.show(activity.supportFragmentManager, DATABASE_CHANGED_DIALOG_TAG)
}
}
}
private fun startDialog(titleId: Int? = null, private fun startDialog(titleId: Int? = null,
messageId: Int? = null, messageId: Int? = null,
warningId: Int? = null) { warningId: Int? = null) {
@@ -140,11 +167,14 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) { override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply { mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addActionTaskListener(actionTaskListener) addActionTaskListener(actionTaskListener)
addDatabaseFileInfoListener(databaseInfoListener)
getService().checkAction() getService().checkAction()
getService().checkDatabaseInfo()
} }
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener) mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null mBinder = null
} }
@@ -206,6 +236,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
fun unregisterProgressTask() { fun unregisterProgressTask() {
stopDialog() stopDialog()
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener) mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null mBinder = null
@@ -264,6 +295,13 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
, ACTION_DATABASE_LOAD_TASK) , ACTION_DATABASE_LOAD_TASK)
} }
fun startDatabaseReload(fixDuplicateUuid: Boolean) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
}
, ACTION_DATABASE_RELOAD_TASK)
}
fun startDatabaseAssignPassword(databaseUri: Uri, fun startDatabaseAssignPassword(databaseUri: Uri,
masterPasswordChecked: Boolean, masterPasswordChecked: Boolean,
masterPassword: String?, masterPassword: String?,

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtil
class ReloadDatabaseRunnable(private val context: Context,
private val mDatabase: Database,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
: ActionRunnable() {
override fun onStartRun() {
// Clear before we load
mDatabase.clear(UriUtil.getBinaryDir(context))
}
override fun onActionRun() {
try {
mDatabase.reloadData(context.contentResolver,
UriUtil.getBinaryDir(context),
progressTaskUpdater)
}
catch (e: LoadDatabaseException) {
setError(e)
}
if (result.isSuccess) {
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
} else {
mDatabase.clearAndClose(UriUtil.getBinaryDir(context))
}
}
override fun onFinishRun() {
mLoadDatabaseResult?.invoke(result)
}
}

View File

@@ -31,10 +31,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
@@ -330,29 +327,11 @@ class Database {
} }
@Throws(LoadDatabaseException::class) @Throws(LoadDatabaseException::class)
fun loadData(uri: Uri, password: String?, keyfile: Uri?, private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
readOnly: Boolean, openDatabaseKDB: (InputStream) -> DatabaseKDB,
contentResolver: ContentResolver, openDatabaseKDBX: (InputStream) -> DatabaseKDBX) {
cacheDirectory: File,
fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
this.fileUri = uri
isReadOnly = readOnly
if (uri.scheme == "file") {
val file = File(uri.path!!)
isReadOnly = !file.canWrite()
}
// Pass KeyFile Uri as InputStreams
var databaseInputStream: InputStream? = null var databaseInputStream: InputStream? = null
var keyFileInputStream: InputStream? = null
try { try {
// Get keyFile inputStream
keyfile?.let {
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
}
// Load Data, pass Uris as InputStreams // Load Data, pass Uris as InputStreams
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri) val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
?: throw IOException("Database input stream cannot be retrieve") ?: throw IOException("Database input stream cannot be retrieve")
@@ -374,22 +353,10 @@ class Database {
when { when {
// Header of database KDB // Header of database KDB
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB( DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(openDatabaseKDB(databaseInputStream))
cacheDirectory,
fixDuplicateUUID)
.openDatabase(databaseInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
// Header of database KDBX // Header of database KDBX
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX( DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(openDatabaseKDBX(databaseInputStream))
cacheDirectory,
fixDuplicateUUID)
.openDatabase(databaseInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
// Header not recognized // Header not recognized
else -> throw SignatureDatabaseException() else -> throw SignatureDatabaseException()
@@ -397,14 +364,90 @@ class Database {
this.mSearchHelper = SearchHelper() this.mSearchHelper = SearchHelper()
loaded = true loaded = true
} catch (e: LoadDatabaseException) {
throw e
} finally {
databaseInputStream?.close()
}
}
@Throws(LoadDatabaseException::class)
fun loadData(uri: Uri, password: String?, keyfile: Uri?,
readOnly: Boolean,
contentResolver: ContentResolver,
cacheDirectory: File,
fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
// Save database URI
this.fileUri = uri
// Check if the file is writable
this.isReadOnly = readOnly
// Pass KeyFile Uri as InputStreams
var keyFileInputStream: InputStream? = null
try {
// Get keyFile inputStream
keyfile?.let {
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
}
// Read database stream for the first time
readDatabaseStream(contentResolver, uri,
{ databaseInputStream ->
DatabaseInputKDB(cacheDirectory)
.openDatabase(databaseInputStream,
password,
keyFileInputStream,
progressTaskUpdater,
fixDuplicateUUID)
},
{ databaseInputStream ->
DatabaseInputKDBX(cacheDirectory)
.openDatabase(databaseInputStream,
password,
keyFileInputStream,
progressTaskUpdater,
fixDuplicateUUID)
}
)
} catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to load keyfile", e)
throw FileNotFoundDatabaseException()
} catch (e: LoadDatabaseException) { } catch (e: LoadDatabaseException) {
throw e throw e
} catch (e: Exception) { } catch (e: Exception) {
throw FileNotFoundDatabaseException() throw LoadDatabaseException(e)
} finally { } finally {
keyFileInputStream?.close() keyFileInputStream?.close()
databaseInputStream?.close() }
}
@Throws(LoadDatabaseException::class)
fun reloadData(contentResolver: ContentResolver,
cacheDirectory: File,
progressTaskUpdater: ProgressTaskUpdater?) {
// Retrieve the stream from the old database URI
fileUri?.let { oldDatabaseUri ->
readDatabaseStream(contentResolver, oldDatabaseUri,
{ databaseInputStream ->
DatabaseInputKDB(cacheDirectory)
.openDatabase(databaseInputStream,
masterKey,
progressTaskUpdater)
},
{ databaseInputStream ->
DatabaseInputKDBX(cacheDirectory)
.openDatabase(databaseInputStream,
masterKey,
progressTaskUpdater)
}
)
} ?: run {
Log.e(TAG, "Database URI is null, database cannot be reloaded")
throw IODatabaseException()
} }
} }
@@ -426,7 +469,7 @@ class Database {
max: Int = Integer.MAX_VALUE): Group? { max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this, return mSearchHelper?.createVirtualGroupWithSearchResult(this,
searchInfoString, SearchParameters().apply { searchInfoString, SearchParameters().apply {
searchInTitles = false searchInTitles = true
searchInUserNames = false searchInUserNames = false
searchInPasswords = false searchInPasswords = false
searchInUrls = true searchInUrls = true
@@ -531,7 +574,7 @@ class Database {
this.fileUri = uri this.fileUri = uri
} }
fun closeAndClear(filesDirectory: File? = null) { fun clear(filesDirectory: File? = null) {
drawFactory.clearCache() drawFactory.clearCache()
// Delete the cache of the database if present // Delete the cache of the database if present
mDatabaseKDB?.clearCache() mDatabaseKDB?.clearCache()
@@ -544,7 +587,10 @@ class Database {
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to clear the directory cache.", e) Log.e(TAG, "Unable to clear the directory cache.", e)
} }
}
fun clearAndClose(filesDirectory: File? = null) {
clear(filesDirectory)
this.mDatabaseKDB = null this.mDatabaseKDB = null
this.mDatabaseKDBX = null this.mDatabaseKDBX = null
this.fileUri = null this.fileUri = null

View File

@@ -426,6 +426,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryInfo.icon = icon entryInfo.icon = icon
entryInfo.username = username entryInfo.username = username
entryInfo.password = password entryInfo.password = password
entryInfo.creationTime = creationTime
entryInfo.modificationTime = lastModificationTime
entryInfo.expires = expires entryInfo.expires = expires
entryInfo.expiryTime = expiryTime entryInfo.expiryTime = expiryTime
entryInfo.url = url entryInfo.url = url
@@ -456,6 +458,9 @@ class Entry : Node, EntryVersionedInterface<Group> {
icon = newEntryInfo.icon icon = newEntryInfo.icon
username = newEntryInfo.username username = newEntryInfo.username
password = newEntryInfo.password password = newEntryInfo.password
// Update date time, creation time stay as is
lastModificationTime = DateInstant()
lastAccessTime = DateInstant()
expires = newEntryInfo.expires expires = newEntryInfo.expires
expiryTime = newEntryInfo.expiryTime expiryTime = newEntryInfo.expiryTime
url = newEntryInfo.url url = newEntryInfo.url
@@ -464,9 +469,6 @@ class Entry : Node, EntryVersionedInterface<Group> {
database?.binaryPool?.let { binaryPool -> database?.binaryPool?.let { binaryPool ->
addAttachments(binaryPool, newEntryInfo.attachments) addAttachments(binaryPool, newEntryInfo.attachments)
} }
// Update date time
lastAccessTime = DateInstant()
lastModificationTime = DateInstant()
database?.stopManageEntry(this) database?.stopManageEntry(this)
} }

View File

@@ -163,10 +163,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
finalKey = messageDigest.digest() finalKey = messageDigest.digest()
} }
override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
return null
}
override fun createGroup(): GroupKDB { override fun createGroup(): GroupKDB {
return GroupKDB() return GroupKDB()
} }

View File

@@ -43,10 +43,12 @@ import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.VariantDictionary
import org.apache.commons.codec.binary.Hex
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.Text
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -113,7 +115,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
init { init {
kdfList.add(KdfFactory.aesKdf) kdfList.add(KdfFactory.aesKdf)
kdfList.add(KdfFactory.argon2Kdf) kdfList.add(KdfFactory.argon2dKdf)
kdfList.add(KdfFactory.argon2idKdf)
} }
constructor() constructor()
@@ -179,7 +182,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
when (oldCompression) { when (oldCompression) {
CompressionAlgorithm.None -> { CompressionAlgorithm.None -> {
when (newCompression) { when (newCompression) {
CompressionAlgorithm.None -> {} CompressionAlgorithm.None -> {
}
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save // Only in databaseV3.1, in databaseV4 the header is zipped during the save
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) { if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
@@ -197,7 +201,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
CompressionAlgorithm.None -> { CompressionAlgorithm.None -> {
decompressAllBinaries() decompressAllBinaries()
} }
CompressionAlgorithm.GZip -> {} CompressionAlgorithm.GZip -> {
}
} }
} }
} }
@@ -377,36 +382,82 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
try { try {
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
} catch (e : ParserConfigurationException) { } catch (e : ParserConfigurationException) {
Log.e(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)", e) Log.w(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)")
} }
val documentBuilder = documentBuilderFactory.newDocumentBuilder() val documentBuilder = documentBuilderFactory.newDocumentBuilder()
val doc = documentBuilder.parse(keyInputStream) val doc = documentBuilder.parse(keyInputStream)
var xmlKeyFileVersion = 1F
val docElement = doc.documentElement val docElement = doc.documentElement
if (docElement == null || !docElement.nodeName.equals(RootElementName, ignoreCase = true)) { val keyFileChildNodes = docElement.childNodes
// <KeyFile> Root node
if (docElement == null
|| !docElement.nodeName.equals(XML_NODE_ROOT_NAME, ignoreCase = true)) {
return null return null
} }
if (keyFileChildNodes.length < 2)
val children = docElement.childNodes
if (children.length < 2) {
return null return null
} for (keyFileChildPosition in 0 until keyFileChildNodes.length) {
val keyFileChildNode = keyFileChildNodes.item(keyFileChildPosition)
for (i in 0 until children.length) { // <Meta>
val child = children.item(i) if (keyFileChildNode.nodeName.equals(XML_NODE_META_NAME, ignoreCase = true)) {
val metaChildNodes = keyFileChildNode.childNodes
if (child.nodeName.equals(KeyElementName, ignoreCase = true)) { for (metaChildPosition in 0 until metaChildNodes.length) {
val keyChildren = child.childNodes val metaChildNode = metaChildNodes.item(metaChildPosition)
for (j in 0 until keyChildren.length) { // <Version>
val keyChild = keyChildren.item(j) if (metaChildNode.nodeName.equals(XML_NODE_VERSION_NAME, ignoreCase = true)) {
if (keyChild.nodeName.equals(KeyDataElementName, ignoreCase = true)) { val versionChildNodes = metaChildNode.childNodes
val children2 = keyChild.childNodes for (versionChildPosition in 0 until versionChildNodes.length) {
for (k in 0 until children2.length) { val versionChildNode = versionChildNodes.item(versionChildPosition)
val text = children2.item(k) if (versionChildNode.nodeType == Node.TEXT_NODE) {
if (text.nodeType == Node.TEXT_NODE) { val versionText = versionChildNode.textContent.removeSpaceChars()
val txt = text as Text try {
return Base64.decode(txt.nodeValue, BASE_64_FLAG) xmlKeyFileVersion = versionText.toFloat()
Log.i(TAG, "Reading XML KeyFile version : $xmlKeyFileVersion")
} catch (e: Exception) {
Log.e(TAG, "XML Keyfile version cannot be read : $versionText")
}
}
}
}
}
}
// <Key>
if (keyFileChildNode.nodeName.equals(XML_NODE_KEY_NAME, ignoreCase = true)) {
val keyChildNodes = keyFileChildNode.childNodes
for (keyChildPosition in 0 until keyChildNodes.length) {
val keyChildNode = keyChildNodes.item(keyChildPosition)
// <Data>
if (keyChildNode.nodeName.equals(XML_NODE_DATA_NAME, ignoreCase = true)) {
var hashString : String? = null
if (keyChildNode.hasAttributes()) {
val dataNodeAttributes = keyChildNode.attributes
hashString = dataNodeAttributes
.getNamedItem(XML_ATTRIBUTE_DATA_HASH).nodeValue
}
val dataChildNodes = keyChildNode.childNodes
for (dataChildPosition in 0 until dataChildNodes.length) {
val dataChildNode = dataChildNodes.item(dataChildPosition)
if (dataChildNode.nodeType == Node.TEXT_NODE) {
val dataString = dataChildNode.textContent.removeSpaceChars()
when (xmlKeyFileVersion) {
1F -> {
// No hash in KeyFile XML version 1
return Base64.decode(dataString, BASE_64_FLAG)
}
2F -> {
return if (hashString != null
&& checkKeyFileHash(dataString, hashString)) {
Log.i(TAG, "Successful key file hash check.")
Hex.decodeHex(dataString)
} else {
Log.e(TAG, "Unable to check the hash of the key file.")
null
}
}
}
} }
} }
} }
@@ -416,10 +467,26 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} catch (e: Exception) { } catch (e: Exception) {
return null return null
} }
return null return null
} }
private fun checkKeyFileHash(data: String, hash: String): Boolean {
val digest: MessageDigest?
var success = false
try {
digest = MessageDigest.getInstance("SHA-256")
digest?.reset()
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
val dataDigest = digest.digest(Hex.decodeHex(data))
.copyOfRange(0, 4)
.toHexString()
success = dataDigest == hash
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
}
return success
}
override fun newGroupId(): NodeIdUUID { override fun newGroupId(): NodeIdUUID {
var newId: NodeIdUUID var newId: NodeIdUUID
do { do {
@@ -633,11 +700,12 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
private const val DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited private const val DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited
private const val RootElementName = "KeyFile" private const val XML_NODE_ROOT_NAME = "KeyFile"
//private const val MetaElementName = "Meta"; private const val XML_NODE_META_NAME = "Meta"
//private const val VersionElementName = "Version"; private const val XML_NODE_VERSION_NAME = "Version"
private const val KeyElementName = "Key" private const val XML_NODE_KEY_NAME = "Key"
private const val KeyDataElementName = "Data" private const val XML_NODE_DATA_NAME = "Data"
private const val XML_ATTRIBUTE_DATA_HASH = "Hash"
const val BASE_64_FLAG = Base64.NO_WRAP const val BASE_64_FLAG = Base64.NO_WRAP

View File

@@ -27,7 +27,11 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import java.io.* import org.apache.commons.codec.binary.Hex
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
@@ -124,42 +128,35 @@ abstract class DatabaseVersioned<
@Throws(IOException::class) @Throws(IOException::class)
protected fun getFileKey(keyInputStream: InputStream): ByteArray { protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyByteArrayOutputStream = ByteArrayOutputStream() val keyData = keyInputStream.readBytes()
keyInputStream.copyTo(keyByteArrayOutputStream)
val keyData = keyByteArrayOutputStream.toByteArray()
val keyByteArrayInputStream = ByteArrayInputStream(keyData) // Check XML key file
val key = loadXmlKeyFile(keyByteArrayInputStream) val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
if (key != null) { if (xmlKeyByteArray != null) {
return key return xmlKeyByteArray
} }
when (keyData.size.toLong()) { // Check 32 bytes key file
32L -> return keyData when (keyData.size) {
64L -> try { 32 -> return keyData
return hexStringToByteArray(String(keyData)) 64 -> try {
} catch (e: IndexOutOfBoundsException) { return Hex.decodeHex(String(keyData))
} catch (ignoredException: Exception) {
// Key is not base 64, treat it as binary data // Key is not base 64, treat it as binary data
} }
} }
val messageDigest: MessageDigest // Hash file as binary data
try { try {
messageDigest = MessageDigest.getInstance("SHA-256") return MessageDigest.getInstance("SHA-256").digest(keyData)
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported") throw IOException("SHA-256 not supported")
} }
try {
messageDigest.update(keyData)
} catch (e: Exception) {
println(e.toString())
}
return messageDigest.digest()
} }
protected abstract fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
return null
}
open fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean { open fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
if (password == null && !containsKeyFile) if (password == null && !containsKeyFile)
@@ -391,16 +388,5 @@ abstract class DatabaseVersioned<
private const val TAG = "DatabaseVersioned" private const val TAG = "DatabaseVersioned"
val UUID_ZERO = UUID(0, 0) val UUID_ZERO = UUID(0, 0)
fun hexStringToByteArray(s: String): ByteArray {
val len = s.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
i += 2
}
return data
}
} }
} }

View File

@@ -43,20 +43,12 @@ abstract class DatabaseException : Exception {
} }
open class LoadDatabaseException : DatabaseException { open class LoadDatabaseException : DatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_load_database override var errorId: Int = R.string.error_load_database
constructor() : super() constructor() : super()
constructor(throwable: Throwable) : super(throwable) constructor(throwable: Throwable) : super(throwable)
} }
class ArcFourDatabaseException : LoadDatabaseException {
@StringRes
override var errorId: Int = R.string.error_arc4
constructor() : super()
constructor(exception: Throwable) : super(exception)
}
class FileNotFoundDatabaseException : LoadDatabaseException { class FileNotFoundDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.file_not_found_content override var errorId: Int = R.string.file_not_found_content
@@ -67,7 +59,6 @@ class FileNotFoundDatabaseException : LoadDatabaseException {
class InvalidAlgorithmDatabaseException : LoadDatabaseException { class InvalidAlgorithmDatabaseException : LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.invalid_algorithm override var errorId: Int = R.string.invalid_algorithm
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }

View File

@@ -41,6 +41,13 @@ abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
abstract fun openDatabase(databaseInputStream: InputStream, abstract fun openDatabase(databaseInputStream: InputStream,
password: String?, password: String?,
keyInputStream: InputStream?, keyInputStream: InputStream?,
progressTaskUpdater: ProgressTaskUpdater?): PwDb progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean = false): PwDb
@Throws(LoadDatabaseException::class)
abstract fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean = false): PwDb
} }

View File

@@ -45,8 +45,7 @@ import javax.crypto.spec.SecretKeySpec
/** /**
* Load a KDB database file. * Load a KDB database file.
*/ */
class DatabaseInputKDB(cacheDirectory: File, class DatabaseInputKDB(cacheDirectory: File)
private val fixDuplicateUUID: Boolean = false)
: DatabaseInput<DatabaseKDB>(cacheDirectory) { : DatabaseInput<DatabaseKDB>(cacheDirectory) {
private lateinit var mDatabaseToOpen: DatabaseKDB private lateinit var mDatabaseToOpen: DatabaseKDB
@@ -55,7 +54,28 @@ class DatabaseInputKDB(cacheDirectory: File,
override fun openDatabase(databaseInputStream: InputStream, override fun openDatabase(databaseInputStream: InputStream,
password: String?, password: String?,
keyInputStream: InputStream?, keyInputStream: InputStream?,
progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDB { progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
}
}
@Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabaseToOpen.masterKey = masterKey
}
}
@Throws(LoadDatabaseException::class)
private fun openDatabase(databaseInputStream: InputStream,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean,
assignMasterKey: (() -> Unit)? = null): DatabaseKDB {
try { try {
// Load entire file, most of it's encrypted. // Load entire file, most of it's encrypted.
@@ -84,7 +104,7 @@ class DatabaseInputKDB(cacheDirectory: File,
mDatabaseToOpen = DatabaseKDB() mDatabaseToOpen = DatabaseKDB()
mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID
mDatabaseToOpen.retrieveMasterKey(password, keyInputStream) assignMasterKey?.invoke()
// Select algorithm // Select algorithm
when { when {

View File

@@ -25,9 +25,10 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.StreamCipherFactory import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -37,7 +38,6 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
@@ -63,8 +63,7 @@ import javax.crypto.Cipher
import javax.crypto.CipherInputStream import javax.crypto.CipherInputStream
import kotlin.math.min import kotlin.math.min
class DatabaseInputKDBX(cacheDirectory: File, class DatabaseInputKDBX(cacheDirectory: File)
private val fixDuplicateUUID: Boolean = false)
: DatabaseInput<DatabaseKDBX>(cacheDirectory) { : DatabaseInput<DatabaseKDBX>(cacheDirectory) {
private var randomStream: StreamCipher? = null private var randomStream: StreamCipher? = null
@@ -98,12 +97,30 @@ class DatabaseInputKDBX(cacheDirectory: File,
override fun openDatabase(databaseInputStream: InputStream, override fun openDatabase(databaseInputStream: InputStream,
password: String?, password: String?,
keyInputStream: InputStream?, keyInputStream: InputStream?,
progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDBX { progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.retrieveMasterKey(password, keyInputStream)
}
}
@Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.masterKey = masterKey
}
}
@Throws(LoadDatabaseException::class)
private fun openDatabase(databaseInputStream: InputStream,
progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean,
assignMasterKey: (() -> Unit)? = null): DatabaseKDBX {
try { try {
// TODO performance
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
mDatabase = DatabaseKDBX() mDatabase = DatabaseKDBX()
mDatabase.changeDuplicateId = fixDuplicateUUID mDatabase.changeDuplicateId = fixDuplicateUUID
@@ -116,9 +133,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
hashOfHeader = headerAndHash.hash hashOfHeader = headerAndHash.hash
val pbHeader = headerAndHash.header val pbHeader = headerAndHash.header
mDatabase.retrieveMasterKey(password, keyInputStream) assignMasterKey?.invoke()
mDatabase.makeFinalKey(header.masterSeed) mDatabase.makeFinalKey(header.masterSeed)
// TODO performance
progressTaskUpdater?.updateMessage(R.string.decrypting_db) progressTaskUpdater?.updateMessage(R.string.decrypting_db)
val engine: CipherEngine val engine: CipherEngine
@@ -185,10 +201,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
loadInnerHeader(inputStreamXml, header) loadInnerHeader(inputStreamXml, header)
} }
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) try {
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
if (randomStream == null) { } catch (e: Exception) {
throw ArcFourDatabaseException() throw LoadDatabaseException(e)
} }
readDocumentStreamed(createPullParser(inputStreamXml)) readDocumentStreamed(createPullParser(inputStreamXml))
@@ -436,8 +452,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
val strData = readString(xpp) val strData = readString(xpp)
if (strData.isNotEmpty()) { if (strData.isNotEmpty()) {
customIconData = Base64.decode(strData, BASE_64_FLAG) customIconData = Base64.decode(strData, BASE_64_FLAG)
} else {
assert(false)
} }
} else { } else {
readUnknown(xpp) readUnknown(xpp)
@@ -958,7 +972,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
// Create empty binary if not retrieved in pool // Create empty binary if not retrieved in pool
if (binaryRetrieve == null) { if (binaryRetrieve == null) {
binaryRetrieve = mDatabase.buildNewBinary(cacheDirectory, binaryRetrieve = mDatabase.buildNewBinary(cacheDirectory,
compression = false, protection = true, binaryPoolId = id) compression = false, protection = false, binaryPoolId = id)
} }
return binaryRetrieve return binaryRetrieve
} }
@@ -1024,29 +1038,20 @@ class DatabaseInputKDBX(cacheDirectory: File,
return xpp.safeNextText() return xpp.safeNextText()
} }
@Throws(XmlPullParserException::class, IOException::class)
private fun readBase64String(xpp: XmlPullParser): ByteArray {
//readNextNode = false;
Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
val plainText = ByteArray(data.size)
randomStream?.processBytes(data, 0, data.size, plainText, 0)
return plainText
}
return ByteArray(0)
}
@Throws(XmlPullParserException::class, IOException::class) @Throws(XmlPullParserException::class, IOException::class)
private fun readProtectedBase64String(xpp: XmlPullParser): ByteArray? { private fun readProtectedBase64String(xpp: XmlPullParser): ByteArray? {
//(xpp.getEventType() == XmlPullParser.START_TAG);
if (xpp.attributeCount > 0) { if (xpp.attributeCount > 0) {
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected) val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) { if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) {
return readBase64String(xpp) Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
val plainText = ByteArray(data.size)
randomStream?.processBytes(data, 0, data.size, plainText, 0)
return plainText
}
return ByteArray(0)
} }
} }
return null return null
} }

View File

@@ -1,25 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.file.output
open class DatabaseHeaderOutput {
var hashOfHeader: ByteArray? = null
protected set
}

View File

@@ -40,13 +40,16 @@ import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class) class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class)
constructor(private val databaseKDBX: DatabaseKDBX, constructor(private val databaseKDBX: DatabaseKDBX,
private val header: DatabaseHeaderKDBX, private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) : DatabaseHeaderOutput() { outputStream: OutputStream) {
private val los: LittleEndianDataOutputStream private val los: LittleEndianDataOutputStream
private val mos: MacOutputStream private val mos: MacOutputStream
private val dos: DigestOutputStream private val dos: DigestOutputStream
lateinit var headerHmac: ByteArray lateinit var headerHmac: ByteArray
var hashOfHeader: ByteArray? = null
private set
init { init {
val md: MessageDigest val md: MessageDigest

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.readBytes
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.OutputStream
import kotlin.experimental.or
class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) {
private val dataOutputStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
@Throws(IOException::class)
fun output() {
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID)
dataOutputStream.writeInt(4)
if (header.innerRandomStream == null)
throw IOException("Can't write innerRandomStream")
dataOutputStream.writeUInt(header.innerRandomStream!!.id)
val streamKeySize = header.innerRandomStreamKey.size
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey)
dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey)
database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
val protectedBinary = keyBinary.binary
// Force decompression to add binary in header
protectedBinary.decompress()
// Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length() + 1))
// Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (protectedBinary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
}
dataOutputStream.writeByte(flag)
protectedBinary.getInputDataStream().use { inputStream ->
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
dataOutputStream.write(buffer)
}
}
}
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
dataOutputStream.writeInt(0)
}
}

View File

@@ -26,7 +26,7 @@ import java.io.OutputStream
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.SecureRandom import java.security.SecureRandom
abstract class DatabaseOutput<Header : DatabaseHeader> protected constructor(protected var mOS: OutputStream) { abstract class DatabaseOutput<Header : DatabaseHeader> protected constructor(protected var mOutputStream: OutputStream) {
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
protected open fun setIVs(header: Header): SecureRandom { protected open fun setIVs(header: Header): SecureRandom {

View File

@@ -63,7 +63,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
// and remove any orphaned nodes that are no longer part of the tree hierarchy // and remove any orphaned nodes that are no longer part of the tree hierarchy
sortGroupsForOutput() sortGroupsForOutput()
val header = outputHeader(mOS) val header = outputHeader(mOutputStream)
val finalKey = getFinalKey(header) val finalKey = getFinalKey(header)
@@ -85,7 +85,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
cipher.init(Cipher.ENCRYPT_MODE, cipher.init(Cipher.ENCRYPT_MODE,
SecretKeySpec(finalKey, "AES"), SecretKeySpec(finalKey, "AES"),
IvParameterSpec(header.encryptionIV)) IvParameterSpec(header.encryptionIV))
val cos = CipherOutputStream(mOS, cipher) val cos = CipherOutputStream(mOutputStream, cipher)
val bos = BufferedOutputStream(cos) val bos = BufferedOutputStream(cos)
outputPlanGroupAndEntries(bos) outputPlanGroupAndEntries(bos)
bos.flush() bos.flush()

View File

@@ -38,7 +38,6 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
@@ -47,6 +46,7 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import org.bouncycastle.crypto.StreamCipher import org.bouncycastle.crypto.StreamCipher
import org.joda.time.DateTime import org.joda.time.DateTime
import org.xmlpull.v1.XmlSerializer import org.xmlpull.v1.XmlSerializer
@@ -58,6 +58,7 @@ import java.util.*
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
import kotlin.experimental.or
class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@@ -81,20 +82,19 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
throw DatabaseOutputException("No such cipher", e) throw DatabaseOutputException("No such cipher", e)
} }
header = outputHeader(mOS) header = outputHeader(mOutputStream)
val osPlain: OutputStream val osPlain: OutputStream
osPlain = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { osPlain = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
val cos = attachStreamEncryptor(header!!, mOS) val cos = attachStreamEncryptor(header!!, mOutputStream)
cos.write(header!!.streamStartBytes) cos.write(header!!.streamStartBytes)
HashedBlockOutputStream(cos) HashedBlockOutputStream(cos)
} else { } else {
mOS.write(hashOfHeader!!) mOutputStream.write(hashOfHeader!!)
mOS.write(headerHmac!!) mOutputStream.write(headerHmac!!)
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!))
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey!!))
} }
val osXml: OutputStream val osXml: OutputStream
@@ -105,8 +105,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} }
if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml) outputInnerHeader(mDatabaseKDBX, header!!, osXml)
ihOut.output()
} }
outputDatabase(osXml) outputDatabase(osXml)
@@ -122,6 +121,49 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} }
} }
@Throws(IOException::class)
private fun outputInnerHeader(database: DatabaseKDBX,
header: DatabaseHeaderKDBX,
outputStream: OutputStream) {
val dataOutputStream = LittleEndianDataOutputStream(outputStream)
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID)
dataOutputStream.writeInt(4)
if (header.innerRandomStream == null)
throw IOException("Can't write innerRandomStream")
dataOutputStream.writeUInt(header.innerRandomStream!!.id)
val streamKeySize = header.innerRandomStreamKey.size
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey)
dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey)
database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
val protectedBinary = keyBinary.binary
// Force decompression to add binary in header
protectedBinary.decompress()
// Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length() + 1))
// Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (protectedBinary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
}
dataOutputStream.writeByte(flag)
protectedBinary.getInputDataStream().use { inputStream ->
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
dataOutputStream.write(buffer)
}
}
}
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
dataOutputStream.writeInt(0)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun outputDatabase(outputStream: OutputStream) { private fun outputDatabase(outputStream: OutputStream) {
@@ -282,9 +324,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} }
random.nextBytes(header.innerRandomStreamKey) random.nextBytes(header.innerRandomStreamKey)
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) try {
if (randomStream == null) { randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey)
throw DatabaseOutputException("Invalid random cipher") } catch (e: Exception) {
throw DatabaseOutputException(e)
} }
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
@@ -420,41 +463,56 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeObject(name, String(Base64.encode(data, BASE_64_FLAG))) writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
} }
/*
// Normally used by a single entry but obsolete because binaries are in meta tag with kdbx3.1-
// or in file header with kdbx4
// binary.isProtected attribute is not used to create the XML
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeBinary(binary : BinaryAttachment) { private fun writeEntryBinary(binary : BinaryAttachment) {
val binaryLength = binary.length() if (binary.length() > 0) {
if (binaryLength > 0) {
if (binary.isProtected) { if (binary.isProtected) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue) xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
binary.getInputDataStream().use { inputStream ->
binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer -> inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
val encoded = ByteArray(buffer.size) val encoded = ByteArray(buffer.size)
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0) randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray() xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
xml.text(charArray, 0, charArray.size) }
} }
} else { } else {
if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
}
// Write the XML // Write the XML
binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer -> binary.getInputDataStream().use { inputStream ->
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray() inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
xml.text(charArray, 0, charArray.size) xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
} }
} }
} }
} }
*/
// Only uses with kdbx3.1 to write binaries in meta tag
// With kdbx4, don't use this method because binaries are in header file
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() { private fun writeMetaBinaries() {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries) xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily in DatabaseV4 (binary header ref is the order) // Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary -> mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary) xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString()) xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
writeBinary(keyBinary.binary) val binary = keyBinary.binary
if (binary.length() > 0) {
if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
}
// Write the XML
binary.getInputDataStream().use { inputStream ->
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
}
}
}
xml.endTag(null, DatabaseKDBXXML.ElemBinary) xml.endTag(null, DatabaseKDBXXML.ElemBinary)
} }
@@ -523,13 +581,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (protect) { if (protect) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue) xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
val data = value.toString().toByteArray()
val data = value.toString().toByteArray(charset("UTF-8")) val dataLength = data.size
val valLength = data.size if (data.isNotEmpty()) {
val encoded = ByteArray(dataLength)
if (valLength > 0) { randomStream!!.processBytes(data, 0, dataLength, encoded, 0)
val encoded = ByteArray(valLength)
randomStream!!.processBytes(data, 0, valLength, encoded, 0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG))) xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
} }
} else { } else {

View File

@@ -26,9 +26,12 @@ import android.graphics.*
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.os.Build
import android.util.Log import android.util.Log
import android.widget.ImageView import android.widget.ImageView
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.annotation.RequiresApi
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
@@ -87,6 +90,22 @@ class IconDrawableFactory {
remoteViews.setImageViewBitmap(imageId, bitmap) remoteViews.setImageViewBitmap(imageId, bitmap)
} }
/**
* Utility method to assign a drawable to a icon and tint it
*/
@RequiresApi(Build.VERSION_CODES.M)
fun assignDrawableToIcon(superDrawable: SuperDrawable,
tintColor: Int = Color.BLACK): Icon {
val bitmap = superDrawable.drawable.toBitmap()
// Tint bitmap if it's not a custom icon
if (superDrawable.tintable && bitmap.isMutable) {
Canvas(bitmap).drawBitmap(bitmap, 0.0F, 0.0F, Paint().apply {
colorFilter = PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)
})
}
return Icon.createWithBitmap(bitmap)
}
/** /**
* Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed * Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed
*/ */
@@ -309,3 +328,22 @@ fun RemoteViews.assignDatabaseIcon(context: Context,
Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e) Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
} }
} }
@RequiresApi(Build.VERSION_CODES.M)
fun createIconFromDatabaseIcon(context: Context,
iconFactory: IconDrawableFactory,
icon: IconImage,
tintColor: Int = Color.BLACK): Icon? {
try {
return iconFactory.assignDrawableToIcon(
iconFactory.getIconSuperDrawable(context,
icon,
24,
true,
tintColor),
tintColor)
} catch (e: Exception) {
Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
}
return null
}

View File

@@ -244,7 +244,8 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
if (entryInfoKey != null) { if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.password, 1) currentInputConnection.commitText(entryInfoKey!!.password, 1)
} }
actionGoAutomatically() val otpFieldExists = entryInfoKey?.containsCustomField(OTP_TOKEN_FIELD) ?: false
actionGoAutomatically(!otpFieldExists)
} }
KEY_OTP -> { KEY_OTP -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
@@ -280,10 +281,11 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB)) currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
} }
private fun actionGoAutomatically() { private fun actionGoAutomatically(switchToPreviousKeyboardIfAllowed: Boolean = true) {
if (PreferencesUtil.isAutoGoActionEnable(this)) { if (PreferencesUtil.isAutoGoActionEnable(this)) {
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO) currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
if (PreferencesUtil.isKeyboardPreviousFillInEnable(this)) { if (switchToPreviousKeyboardIfAllowed
&& PreferencesUtil.isKeyboardPreviousFillInEnable(this)) {
switchToPreviousKeyboard() switchToPreviousKeyboard()
} }
} }

View File

@@ -39,6 +39,8 @@ class EntryInfo : Parcelable {
var icon: IconImage = IconImageStandard() var icon: IconImage = IconImageStandard()
var username: String = "" var username: String = ""
var password: String = "" var password: String = ""
var creationTime: DateInstant = DateInstant()
var modificationTime: DateInstant = DateInstant()
var expires: Boolean = false var expires: Boolean = false
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH
var url: String = "" var url: String = ""
@@ -55,6 +57,8 @@ class EntryInfo : Parcelable {
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
username = parcel.readString() ?: username username = parcel.readString() ?: username
password = parcel.readString() ?: password password = parcel.readString() ?: password
creationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: creationTime
modificationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: modificationTime
expires = parcel.readInt() != 0 expires = parcel.readInt() != 0
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
url = parcel.readString() ?: url url = parcel.readString() ?: url
@@ -74,6 +78,8 @@ class EntryInfo : Parcelable {
parcel.writeParcelable(icon, flags) parcel.writeParcelable(icon, flags)
parcel.writeString(username) parcel.writeString(username)
parcel.writeString(password) parcel.writeString(password)
parcel.writeParcelable(creationTime, flags)
parcel.writeParcelable(modificationTime, flags)
parcel.writeInt(if (expires) 1 else 0) parcel.writeInt(if (expires) 1 else 0)
parcel.writeParcelable(expiryTime, flags) parcel.writeParcelable(expiryTime, flags)
parcel.writeString(url) parcel.writeString(url)
@@ -91,8 +97,8 @@ class EntryInfo : Parcelable {
return customFields.any { !it.protectedValue.isProtected } return customFields.any { !it.protectedValue.isProtected }
} }
fun isAutoGeneratedField(field: Field): Boolean { fun containsCustomField(label: String): Boolean {
return field.name == OTP_TOKEN_FIELD return customFields.lastOrNull { it.name == label } != null
} }
fun getGeneratedFieldValue(label: String): String { fun getGeneratedFieldValue(label: String): String {

View File

@@ -0,0 +1,82 @@
package com.kunzisoft.keepass.model
import android.content.Context
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import android.text.format.Formatter
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
import java.text.DateFormat
import java.util.*
/**
* Utility data class to get FileDatabaseInfo at a `t` time
*/
data class SnapFileDatabaseInfo(var fileUri: Uri?,
var exists: Boolean,
var lastModification: Long?,
var size: Long?): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readParcelable(Uri::class.java.classLoader),
parcel.readByte() != 0.toByte(),
parcel.readValue(Long::class.java.classLoader) as? Long,
parcel.readValue(Long::class.java.classLoader) as? Long) {
}
fun toString(context: Context): String {
val lastModificationString = DateFormat.getDateTimeInstance()
.format(Date(lastModification ?: 0))
return "$lastModificationString, " +
Formatter.formatFileSize(context, size ?: 0)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(fileUri, flags)
parcel.writeByte(if (exists) 1 else 0)
parcel.writeValue(lastModification)
parcel.writeValue(size)
}
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is SnapFileDatabaseInfo) return false
if (fileUri != other.fileUri) return false
if (exists != other.exists) return false
if (lastModification != other.lastModification) return false
if (size != other.size) return false
return true
}
override fun hashCode(): Int {
var result = fileUri?.hashCode() ?: 0
result = 31 * result + exists.hashCode()
result = 31 * result + (lastModification?.hashCode() ?: 0)
result = 31 * result + (size?.hashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<SnapFileDatabaseInfo> {
override fun createFromParcel(parcel: Parcel): SnapFileDatabaseInfo {
return SnapFileDatabaseInfo(parcel)
}
override fun newArray(size: Int): Array<SnapFileDatabaseInfo?> {
return arrayOfNulls(size)
}
fun fromFileDatabaseInfo(fileDatabaseInfo: FileDatabaseInfo): SnapFileDatabaseInfo {
return SnapFileDatabaseInfo(
fileDatabaseInfo.fileUri,
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModification(),
fileDatabaseInfo.getSize())
}
}
}

View File

@@ -62,12 +62,12 @@ class AdvancedUnlockNotificationService : NotificationService() {
action = ACTION_REMOVE_KEYS action = ACTION_REMOVE_KEYS
} }
val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val deviceCredential = PreferencesUtil.isDeviceCredentialUnlockEnable(this) val biometricUnlockEnabled = PreferencesUtil.isBiometricUnlockEnable(this)
val notificationBuilder = buildNewNotification().apply { val notificationBuilder = buildNewNotification().apply {
setSmallIcon(if (deviceCredential) { setSmallIcon(if (biometricUnlockEnabled) {
R.drawable.notification_ic_device_unlock_24dp
} else {
R.drawable.notification_ic_fingerprint_unlock_24dp R.drawable.notification_ic_fingerprint_unlock_24dp
} else {
R.drawable.notification_ic_device_unlock_24dp
}) })
intent?.let { intent?.let {
setContentTitle(getString(R.string.advanced_unlock)) setContentTitle(getString(R.string.advanced_unlock))

View File

@@ -23,6 +23,7 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import java.util.* import java.util.*
/** /**
@@ -54,6 +55,7 @@ class ClipboardEntryNotificationField : Parcelable {
NotificationFieldId.UNKNOWN -> "" NotificationFieldId.UNKNOWN -> ""
NotificationFieldId.USERNAME -> entryInfo?.username ?: "" NotificationFieldId.USERNAME -> entryInfo?.username ?: ""
NotificationFieldId.PASSWORD -> entryInfo?.password ?: "" NotificationFieldId.PASSWORD -> entryInfo?.password ?: ""
NotificationFieldId.OTP -> entryInfo?.getGeneratedFieldValue(OTP_TOKEN_FIELD) ?: ""
NotificationFieldId.FIELD_A, NotificationFieldId.FIELD_A,
NotificationFieldId.FIELD_B, NotificationFieldId.FIELD_B,
NotificationFieldId.FIELD_C -> entryInfo?.getGeneratedFieldValue(label) ?: "" NotificationFieldId.FIELD_C -> entryInfo?.getGeneratedFieldValue(label) ?: ""
@@ -81,7 +83,7 @@ class ClipboardEntryNotificationField : Parcelable {
} }
enum class NotificationFieldId { enum class NotificationFieldId {
UNKNOWN, USERNAME, PASSWORD, FIELD_A, FIELD_B, FIELD_C; UNKNOWN, USERNAME, PASSWORD, OTP, FIELD_A, FIELD_B, FIELD_C;
companion object { companion object {
val anonymousFieldId: Array<NotificationFieldId> val anonymousFieldId: Array<NotificationFieldId>

View File

@@ -25,6 +25,7 @@ import android.content.Intent
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper.NEVER import com.kunzisoft.keepass.timeout.TimeoutHelper.NEVER
@@ -250,6 +251,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
val containsUsernameToCopy = entry.username.isNotEmpty() val containsUsernameToCopy = entry.username.isNotEmpty()
val containsPasswordToCopy = entry.password.isNotEmpty() val containsPasswordToCopy = entry.password.isNotEmpty()
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(context) && PreferencesUtil.allowCopyPasswordAndProtectedFields(context)
val containsOTPToCopy = entry.containsCustomField(OTP_TOKEN_FIELD)
val containsExtraFieldToCopy = entry.customFields.isNotEmpty() val containsExtraFieldToCopy = entry.customFields.isNotEmpty()
&& (entry.containsCustomFieldsNotProtected() && (entry.containsCustomFieldsNotProtected()
|| ||
@@ -262,7 +264,10 @@ class ClipboardEntryNotificationService : LockNotificationService() {
// If notifications enabled in settings // If notifications enabled in settings
// Don't if application timeout // Don't if application timeout
if (PreferencesUtil.isClipboardNotificationsEnable(context)) { if (PreferencesUtil.isClipboardNotificationsEnable(context)) {
if (containsUsernameToCopy || containsPasswordToCopy || containsExtraFieldToCopy) { if (containsUsernameToCopy
|| containsPasswordToCopy
|| containsOTPToCopy
|| containsExtraFieldToCopy) {
// username already copied, waiting for user's action before copy password. // username already copied, waiting for user's action before copy password.
intent.action = ACTION_NEW_NOTIFICATION intent.action = ACTION_NEW_NOTIFICATION
@@ -282,14 +287,22 @@ class ClipboardEntryNotificationService : LockNotificationService() {
ClipboardEntryNotificationField.NotificationFieldId.PASSWORD, ClipboardEntryNotificationField.NotificationFieldId.PASSWORD,
context.getString(R.string.entry_password))) context.getString(R.string.entry_password)))
} }
// Add OTP
if (containsOTPToCopy) {
notificationFields.add(
ClipboardEntryNotificationField(
ClipboardEntryNotificationField.NotificationFieldId.OTP,
OTP_TOKEN_FIELD))
}
// Add extra fields // Add extra fields
if (containsExtraFieldToCopy) { if (containsExtraFieldToCopy) {
try { try {
var anonymousFieldNumber = 0 var anonymousFieldNumber = 0
entry.customFields.forEach { field -> entry.customFields.forEach { field ->
//If value is not protected or allowed //If value is not protected or allowed
if (!field.protectedValue.isProtected if ((!field.protectedValue.isProtected
|| PreferencesUtil.allowCopyPasswordAndProtectedFields(context)) { || PreferencesUtil.allowCopyPasswordAndProtectedFields(context))
&& field.name != OTP_TOKEN_FIELD) {
notificationFields.add( notificationFields.add(
ClipboardEntryNotificationField( ClipboardEntryNotificationField(
ClipboardEntryNotificationField.NotificationFieldId.anonymousFieldId[anonymousFieldNumber], ClipboardEntryNotificationField.NotificationFieldId.anonymousFieldId[anonymousFieldNumber],

View File

@@ -22,9 +22,8 @@ package com.kunzisoft.keepass.notifications
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Binder import android.os.*
import android.os.Bundle import android.util.Log
import android.os.IBinder
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.GroupActivity import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
@@ -40,6 +39,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
@@ -47,6 +47,7 @@ import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import com.kunzisoft.keepass.utils.LOCK_ACTION import com.kunzisoft.keepass.utils.LOCK_ACTION
import com.kunzisoft.keepass.utils.closeDatabase import com.kunzisoft.keepass.utils.closeDatabase
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@@ -65,6 +66,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private var mAllowFinishAction = AtomicBoolean() private var mAllowFinishAction = AtomicBoolean()
private var mActionRunning = false private var mActionRunning = false
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
private var mIconId: Int = R.drawable.notification_ic_database_load private var mIconId: Int = R.drawable.notification_ic_database_load
private var mTitleId: Int = R.string.database_opened private var mTitleId: Int = R.string.database_opened
private var mMessageId: Int? = null private var mMessageId: Int? = null
@@ -93,6 +96,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
mAllowFinishAction.set(false) mAllowFinishAction.set(false)
} }
} }
fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
mDatabaseInfoListeners.add(databaseInfoListener)
}
fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
mDatabaseInfoListeners.remove(databaseInfoListener)
}
} }
interface ActionTaskListener { interface ActionTaskListener {
@@ -101,6 +112,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun onStopAction(actionTask: String, result: ActionRunnable.Result) fun onStopAction(actionTask: String, result: ActionRunnable.Result)
} }
interface DatabaseInfoListener {
fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
newDatabaseInfo: SnapFileDatabaseInfo)
}
/** /**
* Force to call [ActionTaskListener.onStartAction] if the action is still running * Force to call [ActionTaskListener.onStartAction] if the action is still running
*/ */
@@ -112,6 +128,45 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
} }
} }
fun checkDatabaseInfo() {
mDatabase.fileUri?.let {
val previousDatabaseInfo = mSnapFileDatabaseInfo
val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
FileDatabaseInfo(applicationContext, it))
val oldDatabaseModification = previousDatabaseInfo?.lastModification
val newDatabaseModification = lastFileDatabaseInfo.lastModification
val conditionExists = previousDatabaseInfo != null
&& previousDatabaseInfo.exists != lastFileDatabaseInfo.exists
// To prevent dialog opening too often
val conditionLastModification = (oldDatabaseModification != null && newDatabaseModification != null
&& oldDatabaseModification < newDatabaseModification
&& mLastLocalSaveTime + 5000 < newDatabaseModification)
if (conditionExists || conditionLastModification) {
// Show the dialog only if it's real new info and not a delay after a save
Log.i(TAG, "Database file modified " +
"$previousDatabaseInfo != $lastFileDatabaseInfo ")
// Call listener to indicate a change in database info
if (previousDatabaseInfo != null) {
mDatabaseInfoListeners.forEach { listener ->
listener.onDatabaseInfoChanged(previousDatabaseInfo, lastFileDatabaseInfo)
}
}
mSnapFileDatabaseInfo = lastFileDatabaseInfo
}
}
}
fun saveDatabaseInfo() {
mDatabase.fileUri?.let {
mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
FileDatabaseInfo(applicationContext, it))
Log.i(TAG, "Database file saved $mSnapFileDatabaseInfo")
}
}
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
super.onBind(intent) super.onBind(intent)
return mActionTaskBinder return mActionTaskBinder
@@ -138,6 +193,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
val actionRunnable: ActionRunnable? = when (intentAction) { val actionRunnable: ActionRunnable? = when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent) ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent) ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask()
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent) ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent) ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent) ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
@@ -192,6 +248,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
actionTaskListener.onStopAction(intentAction!!, result) actionTaskListener.onStopAction(intentAction!!, result)
} }
} finally { } finally {
// Save the database info before performing action
if (intentAction == ACTION_DATABASE_LOAD_TASK) {
saveDatabaseInfo()
}
// Save the database info after performing save action
if (intentAction == ACTION_DATABASE_SAVE
|| intent?.getBooleanExtra(SAVE_DATABASE_KEY, false) == true) {
mDatabase.fileUri?.let {
val newSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
FileDatabaseInfo(applicationContext, it))
mLastLocalSaveTime = System.currentTimeMillis()
mSnapFileDatabaseInfo = newSnapFileDatabaseInfo
}
}
removeIntentData(intent) removeIntentData(intent)
TimeoutHelper.releaseTemporarilyDisableTimeout() TimeoutHelper.releaseTemporarilyDisableTimeout()
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) { if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
@@ -214,7 +284,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
} }
return when (intentAction) { return when (intentAction) {
ACTION_DATABASE_LOAD_TASK, null -> { ACTION_DATABASE_LOAD_TASK,
ACTION_DATABASE_RELOAD_TASK,
null -> {
START_STICKY START_STICKY
} }
else -> { else -> {
@@ -248,7 +320,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
else -> { else -> {
when (intentAction) { when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> R.string.creating_database ACTION_DATABASE_CREATE_TASK -> R.string.creating_database
ACTION_DATABASE_LOAD_TASK -> R.string.loading_database ACTION_DATABASE_LOAD_TASK,
ACTION_DATABASE_RELOAD_TASK -> R.string.loading_database
ACTION_DATABASE_SAVE -> R.string.saving_database ACTION_DATABASE_SAVE -> R.string.saving_database
else -> { else -> {
R.string.command_execution R.string.command_execution
@@ -258,13 +331,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
} }
mMessageId = when (intentAction) { mMessageId = when (intentAction) {
ACTION_DATABASE_LOAD_TASK -> null ACTION_DATABASE_LOAD_TASK,
ACTION_DATABASE_RELOAD_TASK -> null
else -> null else -> null
} }
mWarningId = mWarningId =
if (!saveAction if (!saveAction
|| intentAction == ACTION_DATABASE_LOAD_TASK) || intentAction == ACTION_DATABASE_LOAD_TASK
|| intentAction == ACTION_DATABASE_RELOAD_TASK)
null null
else else
R.string.do_not_kill_app R.string.do_not_kill_app
@@ -342,9 +417,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
* Execute action with a coroutine * Execute action with a coroutine
*/ */
private suspend fun executeAction(progressTaskUpdater: ProgressTaskUpdater, private suspend fun executeAction(progressTaskUpdater: ProgressTaskUpdater,
onPreExecute: () -> Unit, onPreExecute: () -> Unit,
onExecute: (ProgressTaskUpdater?) -> ActionRunnable?, onExecute: (ProgressTaskUpdater?) -> ActionRunnable?,
onPostExecute: (result: ActionRunnable.Result) -> Unit) { onPostExecute: (result: ActionRunnable.Result) -> Unit) {
mAllowFinishAction.set(false) mAllowFinishAction.set(false)
TimeoutHelper.temporarilyDisableTimeout() TimeoutHelper.temporarilyDisableTimeout()
@@ -465,6 +540,17 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
} }
} }
private fun buildDatabaseReloadActionTask(): ActionRunnable {
return ReloadDatabaseRunnable(
this,
mDatabase,
this
) { result ->
// No need to add each info to reload database
result.data = Bundle()
}
}
private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? { private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(DATABASE_URI_KEY) return if (intent.hasExtra(DATABASE_URI_KEY)
&& intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY) && intent.hasExtra(MASTER_PASSWORD_CHECKED_KEY)
@@ -770,6 +856,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK" const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK" const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK"
const val ACTION_DATABASE_RELOAD_TASK = "ACTION_DATABASE_RELOAD_TASK"
const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK" const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK" const val ACTION_DATABASE_CREATE_GROUP_TASK = "ACTION_DATABASE_CREATE_GROUP_TASK"
const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK" const val ACTION_DATABASE_UPDATE_GROUP_TASK = "ACTION_DATABASE_UPDATE_GROUP_TASK"
@@ -822,6 +909,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time
const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time
private var mSnapFileDatabaseInfo: SnapFileDatabaseInfo? = null
private var mLastLocalSaveTime: Long = 0
fun getListNodesFromBundle(database: Database, bundle: Bundle): List<Node> { fun getListNodesFromBundle(database: Database, bundle: Bundle): List<Node> {
val nodesAction = ArrayList<Node>() val nodesAction = ArrayList<Node>()
bundle.getParcelableArrayList<NodeId<*>>(GROUPS_ID_KEY)?.forEach { bundle.getParcelableArrayList<NodeId<*>>(GROUPS_ID_KEY)?.forEach {

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.otp package com.kunzisoft.keepass.otp
import com.kunzisoft.keepass.model.OtpModel import com.kunzisoft.keepass.model.OtpModel
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import org.apache.commons.codec.binary.Base32 import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.binary.Base64 import org.apache.commons.codec.binary.Base64
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
@@ -150,16 +151,16 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun setBase32Secret(secret: String) { fun setBase32Secret(secret: String) {
if (isValidBase32(secret)) if (isValidBase32(secret)) {
otpModel.secret = Base32().decode(replaceBase32Chars(secret).toByteArray()) otpModel.secret = Base32().decode(replaceBase32Chars(secret))
else } else
throw IllegalArgumentException() throw IllegalArgumentException()
} }
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun setBase64Secret(secret: String) { fun setBase64Secret(secret: String) {
if (isValidBase64(secret)) if (isValidBase64(secret))
otpModel.secret = Base64().decode(secret.toByteArray()) otpModel.secret = Base64().decode(secret)
else else
throw IllegalArgumentException() throw IllegalArgumentException()
} }
@@ -208,38 +209,24 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
fun isValidBase32(secret: String): Boolean { fun isValidBase32(secret: String): Boolean {
val secretChars = replaceBase32Chars(secret) val secretChars = replaceBase32Chars(secret)
return secretChars.isNotEmpty() && checkBase32Secret(secretChars) return secret.isNotEmpty()
&& (Pattern.matches("^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?$", secretChars))
} }
fun isValidBase64(secret: String): Boolean { fun isValidBase64(secret: String): Boolean {
// TODO replace base 64 chars // TODO replace base 64 chars
return secret.isNotEmpty() && checkBase64Secret(secret) return secret.isNotEmpty()
} && (Pattern.matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", secret))
fun removeLineChars(parameter: String): String {
return parameter.replace("[\\r|\\n|\\t|\\u00A0]+".toRegex(), "")
}
fun removeSpaceChars(parameter: String): String {
return parameter.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
} }
fun replaceBase32Chars(parameter: String): String { fun replaceBase32Chars(parameter: String): String {
// Add 'A' at end if not Base32 length // Add padding '=' at end if not Base32 length
var parameterNewSize = removeSpaceChars(parameter.toUpperCase(Locale.ENGLISH)) var parameterNewSize = parameter.toUpperCase(Locale.ENGLISH).removeSpaceChars()
while (parameterNewSize.length % 8 != 0) { while (parameterNewSize.length % 8 != 0) {
parameterNewSize += 'A' parameterNewSize += '='
} }
return parameterNewSize return parameterNewSize
} }
fun checkBase32Secret(secret: String): Boolean {
return (Pattern.matches("^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?$", secret))
}
fun checkBase64Secret(secret: String): Boolean {
return (Pattern.matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", secret))
}
} }
} }

View File

@@ -24,9 +24,9 @@ import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement.Companion.removeLineChars
import com.kunzisoft.keepass.otp.OtpElement.Companion.removeSpaceChars
import com.kunzisoft.keepass.otp.TokenCalculator.* import com.kunzisoft.keepass.otp.TokenCalculator.*
import com.kunzisoft.keepass.utils.StringUtil.removeLineChars
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -57,13 +57,25 @@ object OtpEntryFields {
private const val DIGITS_KEY = "size" private const val DIGITS_KEY = "size"
private const val STEP_KEY = "step" private const val STEP_KEY = "step"
// HmacOtp KeePass2 values (https://keepass.info/help/base/placeholders.html#hmacotp) // HmacOtp KeePass2 values (https://keepass.info/help/base/placeholders.html#otp)
private const val HMACOTP_SECRET_FIELD = "HmacOtp-Secret" private const val HMACOTP_SECRET_FIELD = "HmacOtp-Secret"
private const val HMACOTP_SECRET_HEX_FIELD = "HmacOtp-Secret-Hex" private const val HMACOTP_SECRET_HEX_FIELD = "HmacOtp-Secret-Hex"
private const val HMACOTP_SECRET_BASE32_FIELD = "HmacOtp-Secret-Base32" private const val HMACOTP_SECRET_BASE32_FIELD = "HmacOtp-Secret-Base32"
private const val HMACOTP_SECRET_BASE64_FIELD = "HmacOtp-Secret-Base64" private const val HMACOTP_SECRET_BASE64_FIELD = "HmacOtp-Secret-Base64"
private const val HMACOTP_SECRET_COUNTER_FIELD = "HmacOtp-Counter" private const val HMACOTP_SECRET_COUNTER_FIELD = "HmacOtp-Counter"
// TimeOtp KeePass2 values
private const val TIMEOTP_SECRET_FIELD = "TimeOtp-Secret"
private const val TIMEOTP_SECRET_HEX_FIELD = "TimeOtp-Secret-Hex"
private const val TIMEOTP_SECRET_BASE32_FIELD = "TimeOtp-Secret-Base32"
private const val TIMEOTP_SECRET_BASE64_FIELD = "TimeOtp-Secret-Base64"
private const val TIMEOTP_LENGTH_FIELD = "TimeOtp-Length"
private const val TIMEOTP_PERIOD_FIELD = "TimeOtp-Period"
private const val TIMEOTP_ALGORITHM_FIELD = "TimeOtp-Algorithm"
private const val TIMEOTP_ALGORITHM_SHA1_VALUE = "HMAC-SHA-1"
private const val TIMEOTP_ALGORITHM_SHA256_VALUE = "HMAC-SHA-256"
private const val TIMEOTP_ALGORITHM_SHA512_VALUE = "HMAC-SHA-512"
// Custom fields (maybe from plugin) // Custom fields (maybe from plugin)
private const val TOTP_SEED_FIELD = "TOTP Seed" private const val TOTP_SEED_FIELD = "TOTP Seed"
private const val TOTP_SETTING_FIELD = "TOTP Settings" private const val TOTP_SETTING_FIELD = "TOTP Settings"
@@ -85,14 +97,17 @@ object OtpEntryFields {
// OTP (HOTP/TOTP) from URL and field from KeePassXC // OTP (HOTP/TOTP) from URL and field from KeePassXC
if (parseOTPUri(getField, otpElement)) if (parseOTPUri(getField, otpElement))
return otpElement return otpElement
// TOTP from KeePass 2.47
if (parseTOTPFromOfficialField(getField, otpElement))
return otpElement
// TOTP from key values (maybe plugin or old KeePassXC) // TOTP from key values (maybe plugin or old KeePassXC)
if (parseTOTPKeyValues(getField, otpElement)) if (parseTOTPKeyValues(getField, otpElement))
return otpElement return otpElement
// TOTP from custom field // TOTP from custom field
if (parseTOTPFromField(getField, otpElement)) if (parseTOTPFromPluginField(getField, otpElement))
return otpElement return otpElement
// HOTP fields from KeePass 2 // HOTP fields from KeePass 2
if (parseHOTPFromField(getField, otpElement)) if (parseHOTPFromOfficialField(getField, otpElement))
return otpElement return otpElement
return null return null
} }
@@ -126,7 +141,7 @@ object OtpEntryFields {
private fun parseOTPUri(getField: (id: String) -> String?, otpElement: OtpElement): Boolean { private fun parseOTPUri(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
val otpPlainText = getField(OTP_FIELD) val otpPlainText = getField(OTP_FIELD)
if (otpPlainText != null && otpPlainText.isNotEmpty() && isOTPUri(otpPlainText)) { if (otpPlainText != null && otpPlainText.isNotEmpty() && isOTPUri(otpPlainText)) {
val uri = Uri.parse(removeSpaceChars(otpPlainText)) val uri = Uri.parse(otpPlainText.removeSpaceChars())
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) { if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) {
Log.e(TAG, "Invalid or missing scheme in uri") Log.e(TAG, "Invalid or missing scheme in uri")
@@ -159,16 +174,16 @@ object OtpEntryFields {
if (nameParam != null && nameParam.isNotEmpty()) { if (nameParam != null && nameParam.isNotEmpty()) {
val userIdArray = nameParam.split(":", "%3A") val userIdArray = nameParam.split(":", "%3A")
if (userIdArray.size > 1) { if (userIdArray.size > 1) {
otpElement.issuer = removeLineChars(userIdArray[0]) otpElement.issuer = userIdArray[0].removeLineChars()
otpElement.name = removeLineChars(userIdArray[1]) otpElement.name = userIdArray[1].removeLineChars()
} else { } else {
otpElement.name = removeLineChars(nameParam) otpElement.name = nameParam.removeLineChars()
} }
} }
val issuerParam = uri.getQueryParameter(ISSUER_URL_PARAM) val issuerParam = uri.getQueryParameter(ISSUER_URL_PARAM)
if (issuerParam != null && issuerParam.isNotEmpty()) if (issuerParam != null && issuerParam.isNotEmpty())
otpElement.issuer = removeLineChars(issuerParam) otpElement.issuer = issuerParam.removeLineChars()
val secretParam = uri.getQueryParameter(SECRET_URL_PARAM) val secretParam = uri.getQueryParameter(SECRET_URL_PARAM)
if (secretParam != null && secretParam.isNotEmpty()) { if (secretParam != null && secretParam.isNotEmpty()) {
@@ -247,8 +262,9 @@ object OtpEntryFields {
encodeParameter(username) encodeParameter(username)
else else
encodeParameter(otpElement.name) encodeParameter(otpElement.name)
val secret = encodeParameter(otpElement.getBase32Secret())
val uriString = StringBuilder("otpauth://$otpAuthority/$issuer%3A$accountName" + val uriString = StringBuilder("otpauth://$otpAuthority/$issuer%3A$accountName" +
"?$SECRET_URL_PARAM=${otpElement.getBase32Secret()}" + "?$SECRET_URL_PARAM=${secret}" +
"&$counterOrPeriodLabel=$counterOrPeriodValue" + "&$counterOrPeriodLabel=$counterOrPeriodValue" +
"&$DIGITS_URL_PARAM=${otpElement.digits}" + "&$DIGITS_URL_PARAM=${otpElement.digits}" +
"&$ISSUER_URL_PARAM=$issuer") "&$ISSUER_URL_PARAM=$issuer")
@@ -262,7 +278,40 @@ object OtpEntryFields {
} }
private fun encodeParameter(parameter: String): String { private fun encodeParameter(parameter: String): String {
return Uri.encode(OtpElement.removeLineChars(parameter)) return Uri.encode(parameter.removeLineChars())
}
private fun parseTOTPFromOfficialField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
val secretField = getField(TIMEOTP_SECRET_FIELD)
val secretHexField = getField(TIMEOTP_SECRET_HEX_FIELD)
val secretBase32Field = getField(TIMEOTP_SECRET_BASE32_FIELD)
val secretBase64Field = getField(TIMEOTP_SECRET_BASE64_FIELD)
val lengthField = getField(TIMEOTP_LENGTH_FIELD)
val periodField = getField(TIMEOTP_PERIOD_FIELD)
val algorithmField = getField(TIMEOTP_ALGORITHM_FIELD)
try {
when {
secretField != null -> otpElement.setUTF8Secret(secretField)
secretHexField != null -> otpElement.setHexSecret(secretHexField)
secretBase32Field != null -> otpElement.setBase32Secret(secretBase32Field)
secretBase64Field != null -> otpElement.setBase64Secret(secretBase64Field)
lengthField != null -> otpElement.digits = lengthField.toIntOrNull() ?: OTP_DEFAULT_DIGITS
periodField != null -> otpElement.period = periodField.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
algorithmField != null -> otpElement.algorithm =
when (algorithmField.toUpperCase(Locale.ENGLISH)) {
TIMEOTP_ALGORITHM_SHA1_VALUE -> HashAlgorithm.SHA1
TIMEOTP_ALGORITHM_SHA256_VALUE -> HashAlgorithm.SHA256
TIMEOTP_ALGORITHM_SHA512_VALUE -> HashAlgorithm.SHA512
else -> HashAlgorithm.SHA1
}
else -> return false
}
} catch (exception: Exception) {
return false
}
otpElement.type = OtpType.TOTP
return true
} }
private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean { private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
@@ -290,7 +339,7 @@ object OtpEntryFields {
return false return false
} }
private fun parseTOTPFromField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean { private fun parseTOTPFromPluginField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
val seedField = getField(TOTP_SEED_FIELD) ?: return false val seedField = getField(TOTP_SEED_FIELD) ?: return false
try { try {
otpElement.setBase32Secret(seedField) otpElement.setBase32Secret(seedField)
@@ -316,7 +365,7 @@ object OtpEntryFields {
return true return true
} }
private fun parseHOTPFromField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean { private fun parseHOTPFromOfficialField(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
val secretField = getField(HMACOTP_SECRET_FIELD) val secretField = getField(HMACOTP_SECRET_FIELD)
val secretHexField = getField(HMACOTP_SECRET_HEX_FIELD) val secretHexField = getField(HMACOTP_SECRET_HEX_FIELD)
val secretBase32Field = getField(HMACOTP_SECRET_BASE32_FIELD) val secretBase32Field = getField(HMACOTP_SECRET_BASE32_FIELD)
@@ -382,25 +431,43 @@ object OtpEntryFields {
val totpSeedField = Field(TOTP_SEED_FIELD) val totpSeedField = Field(TOTP_SEED_FIELD)
val totpSettingField = Field(TOTP_SETTING_FIELD) val totpSettingField = Field(TOTP_SETTING_FIELD)
val hmacOtpSecretField = Field(HMACOTP_SECRET_FIELD) val hmacOtpSecretField = Field(HMACOTP_SECRET_FIELD)
val hmacOtpSecretHewField = Field(HMACOTP_SECRET_HEX_FIELD) val hmacOtpSecretHexField = Field(HMACOTP_SECRET_HEX_FIELD)
val hmacOtpSecretBase32Field = Field(HMACOTP_SECRET_BASE32_FIELD) val hmacOtpSecretBase32Field = Field(HMACOTP_SECRET_BASE32_FIELD)
val hmacOtpSecretBase64Field = Field(HMACOTP_SECRET_BASE64_FIELD) val hmacOtpSecretBase64Field = Field(HMACOTP_SECRET_BASE64_FIELD)
val hmacOtpSecretCounterField = Field(HMACOTP_SECRET_COUNTER_FIELD) val hmacOtpSecretCounterField = Field(HMACOTP_SECRET_COUNTER_FIELD)
val timeOtpSecretField = Field(TIMEOTP_SECRET_FIELD)
val timeOtpSecretHexField = Field(TIMEOTP_SECRET_HEX_FIELD)
val timeOtpSecretBase32Field = Field(TIMEOTP_SECRET_BASE32_FIELD)
val timeOtpSecretBase64Field = Field(TIMEOTP_SECRET_BASE64_FIELD)
val timeOtpLengthField = Field(TIMEOTP_LENGTH_FIELD)
val timeOtpPeriodField = Field(TIMEOTP_PERIOD_FIELD)
val timeOtpAlgorithmField = Field(TIMEOTP_ALGORITHM_FIELD)
newCustomFields.remove(otpField) newCustomFields.remove(otpField)
newCustomFields.remove(totpSeedField) newCustomFields.remove(totpSeedField)
newCustomFields.remove(totpSettingField) newCustomFields.remove(totpSettingField)
newCustomFields.remove(hmacOtpSecretField) newCustomFields.remove(hmacOtpSecretField)
newCustomFields.remove(hmacOtpSecretHewField) newCustomFields.remove(hmacOtpSecretHexField)
newCustomFields.remove(hmacOtpSecretBase32Field) newCustomFields.remove(hmacOtpSecretBase32Field)
newCustomFields.remove(hmacOtpSecretBase64Field) newCustomFields.remove(hmacOtpSecretBase64Field)
newCustomFields.remove(hmacOtpSecretCounterField) newCustomFields.remove(hmacOtpSecretCounterField)
newCustomFields.remove(timeOtpSecretField)
newCustomFields.remove(timeOtpSecretHexField)
newCustomFields.remove(timeOtpSecretBase32Field)
newCustomFields.remove(timeOtpSecretBase64Field)
newCustomFields.remove(timeOtpLengthField)
newCustomFields.remove(timeOtpPeriodField)
newCustomFields.remove(timeOtpAlgorithmField)
// Empty auto generated OTP Token field // Empty auto generated OTP Token field
if (fieldsToParse.contains(otpField) if (fieldsToParse.contains(otpField)
|| fieldsToParse.contains(totpSeedField) || fieldsToParse.contains(totpSeedField)
|| fieldsToParse.contains(hmacOtpSecretField) || fieldsToParse.contains(hmacOtpSecretField)
|| fieldsToParse.contains(hmacOtpSecretHewField) || fieldsToParse.contains(hmacOtpSecretHexField)
|| fieldsToParse.contains(hmacOtpSecretBase32Field) || fieldsToParse.contains(hmacOtpSecretBase32Field)
|| fieldsToParse.contains(hmacOtpSecretBase64Field) || fieldsToParse.contains(hmacOtpSecretBase64Field)
|| fieldsToParse.contains(timeOtpSecretField)
|| fieldsToParse.contains(timeOtpSecretHexField)
|| fieldsToParse.contains(timeOtpSecretBase32Field)
|| fieldsToParse.contains(timeOtpSecretBase64Field)
) )
newCustomFields.add(Field(OTP_TOKEN_FIELD)) newCustomFields.add(Field(OTP_TOKEN_FIELD))
return newCustomFields return newCustomFields

View File

@@ -19,10 +19,12 @@
*/ */
package com.kunzisoft.keepass.settings package com.kunzisoft.keepass.settings
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistAppIdPreferenceDialogFragmentCompat import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistAppIdPreferenceDialogFragmentCompat
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistWebDomainPreferenceDialogFragmentCompat import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistWebDomainPreferenceDialogFragmentCompat
@@ -32,6 +34,11 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
// Load the preferences from an XML resource // Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences_autofill, rootKey) setPreferencesFromResource(R.xml.preferences_autofill, rootKey)
val autofillInlineSuggestionsPreference: SwitchPreference? = findPreference(getString(R.string.autofill_inline_suggestions_key))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
autofillInlineSuggestionsPreference?.isVisible = false
}
} }
override fun onDisplayPreferenceDialog(preference: Preference?) { override fun onDisplayPreferenceDialog(preference: Preference?) {

View File

@@ -103,6 +103,6 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
} }
interface Callback { interface Callback {
fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen) fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean = false)
} }
} }

View File

@@ -41,7 +41,7 @@ import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
import com.kunzisoft.keepass.activities.stylish.Stylish import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.app.database.CipherDatabaseAction import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.education.Education import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconPackChooser import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
@@ -218,7 +218,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
val tempAdvancedUnlockPreference: SwitchPreference? = findPreference(getString(R.string.temp_advanced_unlock_enable_key)) val tempAdvancedUnlockPreference: SwitchPreference? = findPreference(getString(R.string.temp_advanced_unlock_enable_key))
val biometricUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val biometricUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
BiometricUnlockDatabaseHelper.biometricUnlockSupported(activity) AdvancedUnlockManager.biometricUnlockSupported(activity)
} else false } else false
biometricUnlockEnablePreference?.apply { biometricUnlockEnablePreference?.apply {
// False if under Marshmallow // False if under Marshmallow
@@ -258,15 +258,18 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
} }
val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
BiometricUnlockDatabaseHelper.deviceCredentialUnlockSupported(activity) AdvancedUnlockManager.deviceCredentialUnlockSupported(activity)
} else false } else false
deviceCredentialUnlockEnablePreference?.apply { deviceCredentialUnlockEnablePreference?.apply {
// Biometric unlock already checked
if (biometricUnlockEnablePreference?.isChecked == true)
isChecked = false
if (!deviceCredentialUnlockSupported) { if (!deviceCredentialUnlockSupported) {
isChecked = false isChecked = false
setOnPreferenceClickListener { preference -> setOnPreferenceClickListener { preference ->
(preference as SwitchPreference).isChecked = false (preference as SwitchPreference).isChecked = false
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.R) UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
.show(parentFragmentManager, "unavailableFeatureDialog") .show(parentFragmentManager, "unavailableFeatureDialog")
false false
} }
@@ -337,9 +340,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
validate?.invoke() validate?.invoke()
deleteKeysAlertDialog?.setOnDismissListener(null) deleteKeysAlertDialog?.setOnDismissListener(null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric( AdvancedUnlockManager.deleteEntryKeyInKeystoreForBiometric(
activity, activity,
object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback { object : AdvancedUnlockManager.AdvancedUnlockErrorCallback {
fun showException(e: Exception) { fun showException(e: Exception) {
Toast.makeText(context, Toast.makeText(context,
getString(R.string.advanced_unlock_scanning_error, e.localizedMessage), getString(R.string.advanced_unlock_scanning_error, e.localizedMessage),
@@ -350,7 +353,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
showException(e) showException(e)
} }
override fun onBiometricException(e: Exception) { override fun onGenericException(e: Exception) {
showException(e) showException(e)
} }
}) })

View File

@@ -552,6 +552,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly) settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
true true
} }
R.id.menu_reload_database -> {
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseReload(false)
return true
}
else -> { else -> {
// Check the time lock before launching settings // Check the time lock before launching settings

View File

@@ -26,6 +26,7 @@ import android.net.Uri
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import java.util.* import java.util.*
@@ -240,14 +241,23 @@ object PreferencesUtil {
fun isBiometricUnlockEnable(context: Context): Boolean { fun isBiometricUnlockEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val biometricSupported = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
AdvancedUnlockManager.biometricUnlockSupported(context)
} else {
false
}
return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key), return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key),
context.resources.getBoolean(R.bool.biometric_unlock_enable_default)) context.resources.getBoolean(R.bool.biometric_unlock_enable_default))
&& biometricSupported
} }
fun isDeviceCredentialUnlockEnable(context: Context): Boolean { fun isDeviceCredentialUnlockEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// Priority to biometric unlock
val biometricAlreadySupported = isBiometricUnlockEnable(context)
return prefs.getBoolean(context.getString(R.string.device_credential_unlock_enable_key), return prefs.getBoolean(context.getString(R.string.device_credential_unlock_enable_key),
context.resources.getBoolean(R.bool.device_credential_unlock_enable_default)) context.resources.getBoolean(R.bool.device_credential_unlock_enable_default))
&& !biometricAlreadySupported
} }
fun isTempAdvancedUnlockEnable(context: Context): Boolean { fun isTempAdvancedUnlockEnable(context: Context): Boolean {
@@ -426,13 +436,18 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.autofill_close_database_default)) context.resources.getBoolean(R.bool.autofill_close_database_default))
} }
fun isAutofillAutoSearchEnable(context: Context): Boolean { fun isAutofillAutoSearchEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key), return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
context.resources.getBoolean(R.bool.autofill_auto_search_default)) context.resources.getBoolean(R.bool.autofill_auto_search_default))
} }
fun isAutofillInlineSuggestionsEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_inline_suggestions_key),
context.resources.getBoolean(R.bool.autofill_inline_suggestions_default))
}
fun isAutofillSaveSearchInfoEnable(context: Context): Boolean { fun isAutofillSaveSearchInfoEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_save_search_info_key), return prefs.getBoolean(context.getString(R.string.autofill_save_search_info_key),

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2020 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.settings
import android.app.Activity import android.app.Activity
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@@ -35,7 +34,9 @@ import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionError import com.kunzisoft.keepass.view.showActionError
@@ -81,7 +82,7 @@ open class SettingsActivity
} }
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout) coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
@@ -94,12 +95,28 @@ open class SettingsActivity
backupManager = BackupManager(this) backupManager = BackupManager(this)
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result -> mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
// Call result in fragment when (actionTask) {
(supportFragmentManager DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?) // Reload the current activity
?.onProgressDialogThreadResult(actionTask, result) startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
else -> {
// Call result in fragment
(supportFragmentManager
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
?.onProgressDialogThreadResult(actionTask, result)
coordinatorLayout?.showActionError(result)
}
}
}
coordinatorLayout?.showActionError(result) // To reload the current screen
if (intent.extras?.containsKey(FRAGMENT_ARG) == true) {
intent.extras?.getString(FRAGMENT_ARG)?.let { fragmentScreenName ->
onNestedPreferenceSelected(NestedSettingsFragment.Screen.valueOf(fragmentScreenName), true)
}
} }
} }
@@ -192,25 +209,33 @@ open class SettingsActivity
hideOrShowLockButton(NestedSettingsFragment.Screen.APPLICATION) hideOrShowLockButton(NestedSettingsFragment.Screen.APPLICATION)
} }
private fun replaceFragment(key: NestedSettingsFragment.Screen) { private fun replaceFragment(key: NestedSettingsFragment.Screen, reload: Boolean) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction().apply {
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, if (reload) {
setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out,
R.anim.slide_in_left, R.anim.slide_out_right) R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED) } else {
.addToBackStack(TAG_NESTED) setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
.commit() R.anim.slide_in_left, R.anim.slide_out_right)
}
replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
addToBackStack(TAG_NESTED)
commit()
}
toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key) toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key)
// To reload the current screen
intent.putExtra(FRAGMENT_ARG, key.name)
hideOrShowLockButton(key) hideOrShowLockButton(key)
} }
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen) { override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
if (mTimeoutEnable) if (mTimeoutEnable)
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) { TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
replaceFragment(key) replaceFragment(key, reload)
} }
else else
replaceFragment(key) replaceFragment(key, reload)
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@@ -225,6 +250,7 @@ open class SettingsActivity
private const val SHOW_LOCK = "SHOW_LOCK" private const val SHOW_LOCK = "SHOW_LOCK"
private const val TITLE_KEY = "TITLE_KEY" private const val TITLE_KEY = "TITLE_KEY"
private const val TAG_NESTED = "TAG_NESTED" private const val TAG_NESTED = "TAG_NESTED"
private const val FRAGMENT_ARG = "FRAGMENT_ARG"
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) { fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
val intent = Intent(activity, SettingsActivity::class.java) val intent = Intent(activity, SettingsActivity::class.java)

View File

@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
cancelAll() cancelAll()
} }
// Clear data // Clear data
Database.getInstance().closeAndClear(UriUtil.getBinaryDir(this)) Database.getInstance().clearAndClose(UriUtil.getBinaryDir(this))
} }

View File

@@ -53,23 +53,19 @@ object MenuUtil {
fun onDefaultMenuOptionsItemSelected(activity: Activity, fun onDefaultMenuOptionsItemSelected(activity: Activity,
item: MenuItem, item: MenuItem,
readOnly: Boolean = READ_ONLY_DEFAULT, readOnly: Boolean = READ_ONLY_DEFAULT,
timeoutEnable: Boolean = false): Boolean { timeoutEnable: Boolean = false) {
when (item.itemId) { when (item.itemId) {
R.id.menu_contribute -> { R.id.menu_contribute -> {
onContributionItemSelected(activity) onContributionItemSelected(activity)
return true
} }
R.id.menu_app_settings -> { R.id.menu_app_settings -> {
// To avoid flickering when launch settings in a LockingActivity // To avoid flickering when launch settings in a LockingActivity
SettingsActivity.launch(activity, readOnly, timeoutEnable) SettingsActivity.launch(activity, readOnly, timeoutEnable)
return true
} }
R.id.menu_about -> { R.id.menu_about -> {
val intent = Intent(activity, AboutActivity::class.java) val intent = Intent(activity, AboutActivity::class.java)
activity.startActivity(intent) activity.startActivity(intent)
return true
} }
else -> return true
} }
} }
} }

View File

@@ -0,0 +1,14 @@
package com.kunzisoft.keepass.utils
object StringUtil {
fun String.removeLineChars(): String {
return this.replace("[\\r|\\n|\\t|\\u00A0]+".toRegex(), "")
}
fun String.removeSpaceChars(): String {
return this.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
}
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
}

View File

@@ -48,8 +48,8 @@ import java.util.*
class EntryContentsView @JvmOverloads constructor(context: Context, class EntryContentsView @JvmOverloads constructor(context: Context,
var attrs: AttributeSet? = null, attrs: AttributeSet? = null,
var defStyle: Int = 0) defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle) { : LinearLayout(context, attrs, defStyle) {
private var fontInVisibility: Boolean = false private var fontInVisibility: Boolean = false
@@ -67,7 +67,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private val creationDateView: TextView private val creationDateView: TextView
private val modificationDateView: TextView private val modificationDateView: TextView
private val lastAccessDateView: TextView
private val expiresImageView: ImageView private val expiresImageView: ImageView
private val expiresDateView: TextView private val expiresDateView: TextView
@@ -117,7 +116,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
creationDateView = findViewById(R.id.entry_created) creationDateView = findViewById(R.id.entry_created)
modificationDateView = findViewById(R.id.entry_modified) modificationDateView = findViewById(R.id.entry_modified)
lastAccessDateView = findViewById(R.id.entry_accessed)
expiresImageView = findViewById(R.id.entry_expires_image) expiresImageView = findViewById(R.id.entry_expires_image)
expiresDateView = findViewById(R.id.entry_expires_date) expiresDateView = findViewById(R.id.entry_expires_date)
@@ -258,20 +256,13 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
modificationDateView.text = date.getDateTimeString(resources) modificationDateView.text = date.getDateTimeString(resources)
} }
fun assignLastAccessDate(date: DateInstant) { fun setExpires(isExpires: Boolean, expiryTime: DateInstant) {
lastAccessDateView.text = date.getDateTimeString(resources)
}
fun setExpires(isExpires: Boolean) {
expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE
} expiresDateView.text = if (isExpires) {
expiryTime.getDateTimeString(resources)
fun assignExpiresDate(date: DateInstant) { } else {
assignExpiresDate(date.getDateTimeString(resources)) resources.getString(R.string.never)
} }
fun assignExpiresDate(constString: String) {
expiresDateView.text = constString
} }
fun assignUUID(uuid: UUID) { fun assignUUID(uuid: UUID) {
@@ -279,7 +270,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
uuidReferenceView.text = UuidUtil.toHexString(uuid) uuidReferenceView.text = UuidUtil.toHexString(uuid)
} }
fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) { fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) {
passwordFieldView.hiddenProtectedValue = hiddenProtectedValue passwordFieldView.hiddenProtectedValue = hiddenProtectedValue
// Hidden style for custom fields // Hidden style for custom fields
@@ -306,7 +296,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
allowCopy: Boolean, allowCopy: Boolean,
onCopyButtonClickListener: OnClickListener?) { onCopyButtonClickListener: OnClickListener?) {
val entryCustomField: EntryField? = EntryField(context, attrs, defStyle) val entryCustomField: EntryField? = EntryField(context)
entryCustomField?.apply { entryCustomField?.apply {
setLabel(title) setLabel(title)
setValue(value.toString(), value.isProtected) setValue(value.toString(), value.isProtected)

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
@@ -20,7 +19,6 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
private val keyFileNameInputLayout: TextInputLayout private val keyFileNameInputLayout: TextInputLayout
private val keyFileNameView: TextView private val keyFileNameView: TextView
private val keyFileOpenView: ImageView
init { init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater? val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
@@ -28,7 +26,6 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
keyFileNameInputLayout = findViewById(R.id.input_entry_keyfile) keyFileNameInputLayout = findViewById(R.id.input_entry_keyfile)
keyFileNameView = findViewById(R.id.keyfile_name) keyFileNameView = findViewById(R.id.keyfile_name)
keyFileOpenView = findViewById(R.id.keyfile_open_button)
} }
override fun setOnClickListener(l: OnClickListener?) { override fun setOnClickListener(l: OnClickListener?) {

View File

@@ -23,7 +23,6 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.text.format.Formatter import android.text.format.Formatter
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import java.io.Serializable import java.io.Serializable
import java.text.DateFormat import java.text.DateFormat
@@ -58,7 +57,11 @@ class FileDatabaseInfo : Serializable {
} }
private set private set
fun getModificationString(): String? { fun getLastModification(): Long? {
return documentFile?.lastModified()
}
fun getLastModificationString(): String? {
return documentFile?.lastModified()?.let { return documentFile?.lastModified()?.let {
if (it != 0L) { if (it != 0L) {
DateFormat.getDateTimeInstance() DateFormat.getDateTimeInstance()
@@ -69,6 +72,10 @@ class FileDatabaseInfo : Serializable {
} }
} }
fun getSize(): Long? {
return documentFile?.length()
}
fun getSizeString(): String? { fun getSizeString(): String? {
return documentFile?.let { return documentFile?.let {
Formatter.formatFileSize(context, it.length()) Formatter.formatFileSize(context, it.length())

View File

@@ -129,7 +129,7 @@ void throwExceptionF(JNIEnv *env, jclass exception, const char *format, ...) {
JNIEXPORT jbyteArray JNIEXPORT jbyteArray
JNICALL Java_com_kunzisoft_keepass_crypto_keyDerivation_Argon2Native_nTransformMasterKey(JNIEnv *env, JNICALL Java_com_kunzisoft_keepass_crypto_keyDerivation_Argon2Native_nTransformMasterKey(JNIEnv *env,
jobject this, jbyteArray password, jbyteArray salt, jint parallelism, jint memory, jobject this, jint type, jbyteArray password, jbyteArray salt, jint parallelism, jint memory,
jint iterations, jbyteArray secretKey, jbyteArray associatedData, jint version) { jint iterations, jbyteArray secretKey, jbyteArray associatedData, jint version) {
argon2_context context; argon2_context context;
@@ -169,7 +169,7 @@ JNICALL Java_com_kunzisoft_keepass_crypto_keyDerivation_Argon2Native_nTransformM
context.flags = ARGON2_DEFAULT_FLAGS; context.flags = ARGON2_DEFAULT_FLAGS;
context.version = (uint32_t) version; context.version = (uint32_t) version;
int argonResult = argon2_ctx(&context, Argon2_d); int argonResult = argon2_ctx(&context, (argon2_type) type);
jbyteArray result; jbyteArray result;
if (argonResult != ARGON2_OK) { if (argonResult != ARGON2_OK) {

View File

@@ -25,6 +25,7 @@
const char *argon2_type2string(argon2_type type, int uppercase) { const char *argon2_type2string(argon2_type type, int uppercase) {
switch (type) { switch (type) {
default:
case Argon2_d: case Argon2_d:
return uppercase ? "Argon2d" : "argon2d"; return uppercase ? "Argon2d" : "argon2d";
case Argon2_i: case Argon2_i:

View File

@@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24" >
<path android:fillColor="#FFFFFF" android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
</vector>

View File

@@ -68,17 +68,10 @@
android:padding="0dp" android:padding="0dp"
android:contentDescription="@string/about" android:contentDescription="@string/about"
android:src="@drawable/ic_launcher_foreground"/> android:src="@drawable/ic_launcher_foreground"/>
<FrameLayout <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_advanced_unlock_container_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent" />
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
android:id="@+id/biometric_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/colorPrimary"
android:visibility="gone"/>
</FrameLayout>
</FrameLayout> </FrameLayout>
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
android:id="@+id/advanced_unlock_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/colorPrimary"
android:visibility="gone"/>
</FrameLayout>

View File

@@ -46,9 +46,9 @@
android:id="@+id/entry_extra_field_edit" android:id="@+id/entry_extra_field_edit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignTop="@+id/entry_extra_field_value_container"
android:src="@drawable/ic_more_white_24dp" android:src="@drawable/ic_more_white_24dp"
android:contentDescription="@string/menu_edit" android:contentDescription="@string/menu_edit"
style="@style/KeepassDXStyle.ImageButton.Simple"/> style="@style/KeepassDXStyle.ImageButton.Simple"/>

View File

@@ -175,12 +175,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/entry_accessed" android:text="@string/entry_accessed"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" /> style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:visibility="gone"
android:id="@+id/entry_accessed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
<!-- Expires --> <!-- Expires -->
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView

View File

@@ -18,7 +18,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/keyfile_open_button"> app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/keyfile_name" android:id="@+id/keyfile_name"
@@ -33,16 +33,4 @@
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:maxLines="1"/> android:maxLines="1"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/keyfile_open_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/content_description_open_file"
android:focusable="true"
android:background="@drawable/background_item_selection"
android:src="@drawable/ic_folder_white_24dp"
style="@style/KeepassDXStyle.ImageButton.Simple" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -22,6 +22,6 @@
<item android:id="@+id/menu_contribute" <item android:id="@+id/menu_contribute"
android:icon="@drawable/ic_heart_white_24dp" android:icon="@drawable/ic_heart_white_24dp"
android:title="@string/contribute" android:title="@string/contribute"
android:orderInCategory="95" android:orderInCategory="99"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
</menu> </menu>

View File

@@ -24,4 +24,9 @@
android:title="@string/menu_save_database" android:title="@string/menu_save_database"
android:orderInCategory="95" android:orderInCategory="95"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item android:id="@+id/menu_reload_database"
android:icon="@drawable/ic_reload_white_24dp"
android:title="@string/menu_reload_database"
android:orderInCategory="96"
app:showAsAction="ifRoom" />
</menu> </menu>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="feedback">Обратна връзка</string>
<string name="encryption_algorithm">Алгоритъм за криптиране</string>
<string name="encryption">Криптиране</string>
<string name="security">Сигурност</string>
<string name="master_key">Главен ключ</string>
<string name="add_group">Добави група</string>
<string name="edit_entry">Редактирай</string>
<string name="add_entry">Добави</string>
<string name="accept">Приемам</string>
</resources>

View File

@@ -19,12 +19,12 @@
Czech translation by Jan Vaněk Czech translation by Jan Vaněk
--><resources> --><resources>
<string name="homepage">Domovská stránka</string> <string name="homepage">Domovská stránka</string>
<string name="about_description">Androidová verze správce hesel KeePass</string> <string name="about_description">Implementace správce hesel KeePass pro Android</string>
<string name="accept">Přijmout</string> <string name="accept">Přijmout</string>
<string name="add_entry">Přidat záznam</string> <string name="add_entry">Přidat záznam</string>
<string name="add_group">Přidat skupinu</string> <string name="add_group">Přidat skupinu</string>
<string name="encryption_algorithm">Šifrovací algoritmus</string> <string name="encryption_algorithm">Šifrovací algoritmus</string>
<string name="app_timeout">Časový limit aplikace</string> <string name="app_timeout">Časový limit</string>
<string name="app_timeout_summary">Doba nečinnosti, po které se aplikace zamkne</string> <string name="app_timeout_summary">Doba nečinnosti, po které se aplikace zamkne</string>
<string name="application">Aplikace</string> <string name="application">Aplikace</string>
<string name="menu_app_settings">Nastavení aplikace</string> <string name="menu_app_settings">Nastavení aplikace</string>
@@ -35,16 +35,16 @@
<string name="clipboard_error">Některá zařízení nedovolují aplikacím používat schránku.</string> <string name="clipboard_error">Některá zařízení nedovolují aplikacím používat schránku.</string>
<string name="clipboard_error_clear">Nelze vyprázdnit schránku</string> <string name="clipboard_error_clear">Nelze vyprázdnit schránku</string>
<string name="clipboard_timeout">Časový limit schránky</string> <string name="clipboard_timeout">Časový limit schránky</string>
<string name="clipboard_timeout_summary">Doba uchování ve schránce</string> <string name="clipboard_timeout_summary">Doba uchování ve schránce (je-li podporována zařízením)</string>
<string name="select_to_copy">Vyberte zkopírovat %1$s do schránky</string> <string name="select_to_copy">Vyberte zkopírovat %1$s do schránky</string>
<string name="retrieving_db_key">Načítá klíče databáze…</string> <string name="retrieving_db_key">Načítám klíč databáze…</string>
<string name="database">Databáze</string> <string name="database">Databáze</string>
<string name="decrypting_db">Dešifrování obsahu databáze…</string> <string name="decrypting_db">Dešifruji obsah databáze…</string>
<string name="default_checkbox">Použít jako výchozí databázi</string> <string name="default_checkbox">Použít jako výchozí databázi</string>
<string name="digits">Číslice</string> <string name="digits">Číslice</string>
<string name="select_database_file">Otevřít existující databázi</string> <string name="select_database_file">Otevřít existující databázi</string>
<string name="entry_accessed">Poslední přístup</string> <string name="entry_accessed">Poslední přístup</string>
<string name="entry_cancel">Storno</string> <string name="entry_cancel">Zrušit</string>
<string name="entry_notes">Poznámky</string> <string name="entry_notes">Poznámky</string>
<string name="entry_confpassword">Potvrďte heslo</string> <string name="entry_confpassword">Potvrďte heslo</string>
<string name="entry_created">Vytvořeno</string> <string name="entry_created">Vytvořeno</string>
@@ -55,27 +55,27 @@
<string name="entry_password">Heslo</string> <string name="entry_password">Heslo</string>
<string name="save">Uložit</string> <string name="save">Uložit</string>
<string name="entry_title">Název</string> <string name="entry_title">Název</string>
<string name="entry_url">URL adresa</string> <string name="entry_url">URL</string>
<string name="entry_user_name">Uživatelské jméno</string> <string name="entry_user_name">Uživatelské jméno</string>
<string name="error_arc4">Arcfour proudová šifra není podporována.</string> <string name="error_arc4">Arcfour proudová šifra není podporována.</string>
<string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string> <string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string>
<string name="error_file_not_create">Soubor se nedaří vytvořit</string> <string name="error_file_not_create">Soubor se nepodařilo vytvořit</string>
<string name="error_invalid_db">Databázi nelze číst.</string> <string name="error_invalid_db">Databázi se nepodařilo načíst.</string>
<string name="error_invalid_path">Ujistěte se, že cesta je správná.</string> <string name="error_invalid_path">Ujistěte se, že cesta je správná.</string>
<string name="error_no_name">Zadejte jméno.</string> <string name="error_no_name">Zadejte jméno.</string>
<string name="error_nokeyfile">Vyberte soubor s klíčem.</string> <string name="error_nokeyfile">Vyberte soubor s klíčem.</string>
<string name="error_out_of_memory">Nedostatek paměti k načtení celé databáze.</string> <string name="error_out_of_memory">Nedostatek paměti k načtení celé databáze.</string>
<string name="error_pass_gen_type">Je třeba zvolit alespoň jeden způsob vytváření hesla.</string> <string name="error_pass_gen_type">Je třeba zvolit alespoň jeden způsob vytváření hesla.</string>
<string name="error_pass_match">Zadaná hesla se neshodují.</string> <string name="error_pass_match">Hesla se neshodují.</string>
<string name="error_rounds_too_large">Příliš vysoký „Počet průchodů“. Nastavuji na 2147483648.</string> <string name="error_rounds_too_large">Příliš vysoký „Počet průchodů“. Nastavuji na 2147483648.</string>
<string name="error_string_key">Je třeba, aby každý řetězec měl název kolonky.</string> <string name="error_string_key">Je třeba, aby každý řetězec měl název kolonky.</string>
<string name="error_wrong_length">Do pole „Délka“ zadejte celé kladné číslo.</string> <string name="error_wrong_length">Do pole „Délka“ zadejte celé kladné číslo.</string>
<string name="field_name">Název pole</string> <string name="field_name">Název kolonky</string>
<string name="field_value">Hodnota pole</string> <string name="field_value">Hodnota kolonky</string>
<string name="file_browser">Správce souborů</string> <string name="file_browser">Správce souborů</string>
<string name="generate_password">Vytvoř heslo</string> <string name="generate_password">Generovat heslo</string>
<string name="hint_conf_pass">Potvrdit heslo</string> <string name="hint_conf_pass">Potvrdit heslo</string>
<string name="hint_generated_password">Vytvořené heslo</string> <string name="hint_generated_password">Generované heslo</string>
<string name="hint_group_name">Název skupiny</string> <string name="hint_group_name">Název skupiny</string>
<string name="hint_keyfile">Soubor s klíčem</string> <string name="hint_keyfile">Soubor s klíčem</string>
<string name="hint_length">Délka</string> <string name="hint_length">Délka</string>
@@ -83,8 +83,8 @@
<string name="password">Heslo</string> <string name="password">Heslo</string>
<string name="invalid_credentials">Nebylo možno načíst autentizační údaje.</string> <string name="invalid_credentials">Nebylo možno načíst autentizační údaje.</string>
<string name="invalid_algorithm">Nesprávný algoritmus.</string> <string name="invalid_algorithm">Nesprávný algoritmus.</string>
<string name="invalid_db_sig">Nedaří se rozpoznat formát databáze.</string> <string name="invalid_db_sig">Nepodařilo se rozpoznat formát databáze.</string>
<string name="keyfile_is_empty">Soubor s klíčem je prázdný.</string> <string name="keyfile_is_empty">Soubor klíče je prázdný.</string>
<string name="length">Délka</string> <string name="length">Délka</string>
<string name="list_size_title">Velikost položek seznamu</string> <string name="list_size_title">Velikost položek seznamu</string>
<string name="list_size_summary">Velikost textu v seznamu prvků</string> <string name="list_size_summary">Velikost textu v seznamu prvků</string>
@@ -97,28 +97,28 @@
<string name="settings">Nastavení</string> <string name="settings">Nastavení</string>
<string name="menu_database_settings">Nastavení databáze</string> <string name="menu_database_settings">Nastavení databáze</string>
<string name="menu_delete">Smazat</string> <string name="menu_delete">Smazat</string>
<string name="menu_donate">Podpořit vývoj darem</string> <string name="menu_donate">Přispět darem</string>
<string name="menu_edit">Upravit</string> <string name="menu_edit">Upravit</string>
<string name="menu_hide_password">Skrýt heslo</string> <string name="menu_hide_password">Skrýt heslo</string>
<string name="menu_lock">Zamknout databázi</string> <string name="menu_lock">Zamknout databázi</string>
<string name="menu_open">Otevřít</string> <string name="menu_open">Otevřít</string>
<string name="menu_search">Hledat</string> <string name="menu_search">Hledat</string>
<string name="menu_showpass">Uk heslo</string> <string name="menu_showpass">Ukázat heslo</string>
<string name="menu_url">Jít na URL</string> <string name="menu_url">Přejít na URL</string>
<string name="minus">Mínus</string> <string name="minus">Mínus</string>
<string name="never">Nikdy</string> <string name="never">Nikdy</string>
<string name="no_results">Žádné výsledky hledání</string> <string name="no_results">Žádné výsledky hledání</string>
<string name="no_url_handler">Pro otevření tohoto URL nainstalujte webový prohlížeč.</string> <string name="no_url_handler">Pro otevření tohoto URL nainstalujte webový prohlížeč.</string>
<string name="omit_backup_search_title">Neprohledávat položky v záloze</string> <string name="omit_backup_search_title">Neprohledávat položky v záloze</string>
<string name="omit_backup_search_summary">Vynechat skupiny „Záloha“ a \"Koš\" z výsledků vyhledávání</string> <string name="omit_backup_search_summary">Vynechat skupiny „Záloha“ a \"Koš\" z výsledků vyhledávání</string>
<string name="progress_create">Vytvářím novou databázi…</string> <string name="progress_create">Zakládám novou databázi…</string>
<string name="progress_title">Zpracování</string> <string name="progress_title">Pracuji</string>
<string name="protection">Ochrana</string> <string name="protection">Ochrana</string>
<string name="read_only_warning">Ke změně v databázi potřebuje KeePassDX oprávnění pro zápis.</string> <string name="read_only_warning">Ke změně v databáze potřebuje KeePassDX oprávnění pro zápis.</string>
<string name="content_description_remove_from_list">Odstranit</string> <string name="content_description_remove_from_list">Odstranit</string>
<string name="encryption_rijndael">Rijndael (AES)</string> <string name="encryption_rijndael">Rijndael (AES)</string>
<string name="root">Kořen</string> <string name="root">Kořen</string>
<string name="rounds">Počet šifrovacích průchodů</string> <string name="rounds">Transformační průchody</string>
<string name="rounds_explanation">Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.</string> <string name="rounds_explanation">Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.</string>
<string name="saving_database">Ukládám databázi…</string> <string name="saving_database">Ukládám databázi…</string>
<string name="space">Místo</string> <string name="space">Místo</string>
@@ -134,7 +134,7 @@
<string name="version_label">Verze %1$s</string> <string name="version_label">Verze %1$s</string>
<string name="education_unlock_summary">Databázi odemknete zadáním hesla a/nebo souboru s klíčem. <string name="education_unlock_summary">Databázi odemknete zadáním hesla a/nebo souboru s klíčem.
\n \n
\nNezapomeňte si po každé úpravě zazálohovat kopii svého .kdbx souboru na bezpečné místo.</string> \nNezapomeňte po každé úpravě zálohovat kopii svého .kdbx souboru na bezpečné místo.</string>
<string-array name="timeout_options"> <string-array name="timeout_options">
<item>5 sekund</item> <item>5 sekund</item>
<item>10 sekund</item> <item>10 sekund</item>
@@ -155,41 +155,41 @@
<string name="encryption">Šifrování</string> <string name="encryption">Šifrování</string>
<string name="key_derivation_function">Funkce pro tvorbu klíče</string> <string name="key_derivation_function">Funkce pro tvorbu klíče</string>
<string name="extended_ASCII">Rozšířené ASCII</string> <string name="extended_ASCII">Rozšířené ASCII</string>
<string name="allow">Umožnit</string> <string name="allow">Povolit</string>
<string name="error_load_database">Databázi se nedaří načíst.</string> <string name="error_load_database">Databázi se nepodařilo načíst.</string>
<string name="error_load_database_KDF_memory">Klíč se nedaří načíst, zkuste snížit množství paměti, využívané funkcí pro tvorbu klíče.</string> <string name="error_load_database_KDF_memory">Klíč se nepodařilo načíst, zkuste snížit \"využití paměti\" pro KDF.</string>
<string name="error_autofill_enable_service">Službu automatického vyplňování se nedaří zapnout.</string> <string name="error_autofill_enable_service">Službu automatického vyplňování se nepodařilo zapnout.</string>
<string name="error_move_folder_in_itself">Není možné přesunout skupinu do ní samotné.</string> <string name="error_move_folder_in_itself">Není možné přesunout skupinu do ní samotné.</string>
<string name="file_not_found_content">Soubor nenalezen. Zkuste jej otevřít ze správce souborů.</string> <string name="file_not_found_content">Soubor nenalezen. Zkuste jej otevřít ze správce souborů.</string>
<string name="list_entries_show_username_title">Zobrazit uživatelská jména</string> <string name="list_entries_show_username_title">Zobrazit uživatelská jména</string>
<string name="list_entries_show_username_summary">V seznamech položek zobrazit uživatelská jména</string> <string name="list_entries_show_username_summary">V seznamech záznamů zobrazit uživatelská jména</string>
<string name="copy_field">Kopie %1$s</string> <string name="copy_field">Kopie %1$s</string>
<string name="menu_form_filling_settings">Vyplňování formulářů</string> <string name="menu_form_filling_settings">Vyplňování formulářů</string>
<string name="menu_copy">Zkopírovat</string> <string name="menu_copy">Zkopírovat</string>
<string name="menu_move">Přesunout</string> <string name="menu_move">Přesunout</string>
<string name="menu_paste">Vložit</string> <string name="menu_paste">Vložit</string>
<string name="menu_cancel">Storno</string> <string name="menu_cancel">Zrušit</string>
<string name="menu_file_selection_read_only">Chráněno před zápisem</string> <string name="menu_file_selection_read_only">Chráněno před zápisem</string>
<string name="menu_open_file_read_and_write">Čtení a zápis</string> <string name="menu_open_file_read_and_write">Čtení a zápis</string>
<string name="read_only">Chráněno před zápisem</string> <string name="read_only">Chráněno před zápisem</string>
<string name="encryption_explanation">Algoritmus šifrování databáze užitý pro všechna data.</string> <string name="encryption_explanation">Algoritmus šifrování databáze použit pro všechna data.</string>
<string name="kdf_explanation">Klíč pro šifrovací algoritmus je vytvořen transformací hlavního klíče skrze odvozovací funkci klíče s náhodně přidanou složkou, tzv. solí.</string> <string name="kdf_explanation">Klíč pro šifrovací algoritmus je vytvořen transformací hlavního klíče pomocí funkce odvození klíče s náhodně přidanou složkou, tzv. solí.</string>
<string name="memory_usage">Využití paměti</string> <string name="memory_usage">Využití paměti</string>
<string name="memory_usage_explanation">Množství paměti (v bajtech) použitých funkcí pro odvození klíče.</string> <string name="memory_usage_explanation">Množství paměti (v bajtech) použitých funkcí pro odvození klíče.</string>
<string name="parallelism">Souběžné zpracovávání</string> <string name="parallelism">Souběžné zpracovávání</string>
<string name="parallelism_explanation">Stupeň souběžného zpracovávání (počet vláken) použitý funkcí pro vytvoření klíče.</string> <string name="parallelism_explanation">Stupeň souběžného zpracovávání (počet vláken) použitý funkcí pro odvození klíče.</string>
<string name="sort_menu">Seřadit</string> <string name="sort_menu">Seřadit</string>
<string name="sort_ascending">Nejnižší první ↓</string> <string name="sort_ascending">Nejnižší první ↓</string>
<string name="sort_groups_before">Skupiny první</string> <string name="sort_groups_before">Skupiny první</string>
<string name="sort_recycle_bin_bottom">Koš jako poslední</string> <string name="sort_recycle_bin_bottom">Koš jako poslední</string>
<string name="sort_title">Nadpis</string> <string name="sort_title">Nadpis</string>
<string name="sort_username">Uživatelské jméno</string> <string name="sort_username">Uživatelské jméno</string>
<string name="sort_creation_time">Vytvoření</string> <string name="sort_creation_time">Založeno</string>
<string name="sort_last_modify_time">Změna</string> <string name="sort_last_modify_time">Změněno</string>
<string name="sort_last_access_time">Přístup</string> <string name="sort_last_access_time">Přístup</string>
<string name="warning">Varování</string> <string name="warning">Varování</string>
<string name="warning_password_encoding">Nepoužívejte v hesle pro databázový soubor znaky mimo znakovou sadu Latin-1 (nepoužívejte znaky s diakritikou).</string> <string name="warning_password_encoding">Nepoužívejte v hesle pro databázový soubor znaky mimo znaky kódování textu (nerozpoznané znaky budou konvertovány na stejné písmeno).</string>
<string name="warning_empty_password">Pokračovat bez ochrany odemknutím heslem\?</string> <string name="warning_empty_password">Pokračovat bez ochrany odemknutí heslem\?</string>
<string name="warning_no_encryption_key">Pokračovat bez šifrovacího klíče\?</string> <string name="warning_no_encryption_key">Pokračovat bez šifrovacího klíče\?</string>
<string name="encrypted_value_stored">Šifrované heslo uloženo</string> <string name="encrypted_value_stored">Šifrované heslo uloženo</string>
<string name="no_credentials_stored">Tato databáze zatím nemá uložené heslo.</string> <string name="no_credentials_stored">Tato databáze zatím nemá uložené heslo.</string>
@@ -198,7 +198,7 @@
<string name="general">Obecné</string> <string name="general">Obecné</string>
<string name="autofill">Automatické vyplnění</string> <string name="autofill">Automatické vyplnění</string>
<string name="autofill_service_name">KeePassDX automatické vyplňování formulářů</string> <string name="autofill_service_name">KeePassDX automatické vyplňování formulářů</string>
<string name="autofill_sign_in_prompt">Přihlásit se pomocí KeePassDX</string> <string name="autofill_sign_in_prompt">Přihlásit se s KeePassDX</string>
<string name="set_autofill_service_title">Nastavit výchozí službu automatického vyplňování</string> <string name="set_autofill_service_title">Nastavit výchozí službu automatického vyplňování</string>
<string name="autofill_explanation_summary">Povolit rychlé automatické vyplňování formulářů v ostatních aplikacích</string> <string name="autofill_explanation_summary">Povolit rychlé automatické vyplňování formulářů v ostatních aplikacích</string>
<string name="password_size_title">Délka generovaného hesla</string> <string name="password_size_title">Délka generovaného hesla</string>
@@ -206,28 +206,28 @@
<string name="list_password_generator_options_title">Znaky hesla</string> <string name="list_password_generator_options_title">Znaky hesla</string>
<string name="list_password_generator_options_summary">Nastavit povolené znaky pro generátor hesel</string> <string name="list_password_generator_options_summary">Nastavit povolené znaky pro generátor hesel</string>
<string name="clipboard">Schránka</string> <string name="clipboard">Schránka</string>
<string name="clipboard_notifications_title">Oznamování schránky</string> <string name="clipboard_notifications_title">Oznámení schránky</string>
<string name="clipboard_notifications_summary">Ukázat oznamení schránky o kopírování pole při prohlížení záznamu</string> <string name="clipboard_notifications_summary">Ukázat oznámení schránky o kopírování pole při prohlížení záznamu</string>
<string name="clipboard_warning">Vymazat historii schránky manuálně, pokud automatické vymazání schránky selže.</string> <string name="clipboard_warning">Vymazat historii schránky manuálně, pokud automatické vymazání schránky selže.</string>
<string name="lock">Zamknout</string> <string name="lock">Zamknout</string>
<string name="lock_database_screen_off_title">Zámek obrazovky</string> <string name="lock_database_screen_off_title">Zámek obrazovky</string>
<string name="lock_database_screen_off_summary">Při zhasnutí obrazovky uzamknout databázi</string> <string name="lock_database_screen_off_summary">Při zhasnutí obrazovky uzamknout databázi</string>
<string name="advanced_unlock">Pokročilé odemčení</string> <string name="advanced_unlock">Rozšířené odemknutí</string>
<string name="biometric_unlock_enable_title">Biometrické odemčení</string> <string name="biometric_unlock_enable_title">Biometrické odemknutí</string>
<string name="biometric_unlock_enable_summary">Nechá otevřít databázi snímáním biometrického údaje</string> <string name="biometric_unlock_enable_summary">Nechá otevřít databázi snímáním biometrického údaje</string>
<string name="biometric_delete_all_key_title">Smazat šifrovací klíče</string> <string name="biometric_delete_all_key_title">Smazat šifrovací klíče</string>
<string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí</string> <string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí</string>
<string name="unavailable_feature_text">Tuto funkci se nedaří spustit.</string> <string name="unavailable_feature_text">Tuto funkci se nedaří spustit.</string>
<string name="unavailable_feature_version">V zařízení je instalován Android %1$s, ale potřebná je verze %2$s a novější.</string> <string name="unavailable_feature_version">V zařízení je instalován Android %1$s, ale potřebná je verze %2$s a novější.</string>
<string name="unavailable_feature_hardware">Hardware nebyl rozpoznán.</string> <string name="unavailable_feature_hardware">Odpovídající hardware nebyl rozpoznán.</string>
<string name="file_name">Název souboru</string> <string name="file_name">Název souboru</string>
<string name="path">Cesta</string> <string name="path">Cesta</string>
<string name="assign_master_key">Přiřadit hlavní klíč</string> <string name="assign_master_key">Přiřadit hlavní klíč</string>
<string name="create_keepass_file">Vytvořit novou databázi</string> <string name="create_keepass_file">Založit novou databázi</string>
<string name="recycle_bin_title">Využití koše</string> <string name="recycle_bin_title">Využití koše</string>
<string name="recycle_bin_summary">Před smazáním přesune skupiny a položky do skupiny „Koš“</string> <string name="recycle_bin_summary">Před smazáním přesune skupiny a položky do skupiny „Koš“</string>
<string name="monospace_font_fields_enable_title">Písmo položek</string> <string name="monospace_font_fields_enable_title">Písmo kolonek</string>
<string name="monospace_font_fields_enable_summary">Čitelnost znaků v položkách můžete přizpůsobit změnou písma</string> <string name="monospace_font_fields_enable_summary">Čitelnost znaků v kolonkách můžete přizpůsobit změnou písma</string>
<string name="allow_copy_password_title">Důvěřovat schránce</string> <string name="allow_copy_password_title">Důvěřovat schránce</string>
<string name="allow_copy_password_summary">Povolit kopírování hesla záznamu a chráněných položek do schránky</string> <string name="allow_copy_password_summary">Povolit kopírování hesla záznamu a chráněných položek do schránky</string>
<string name="allow_copy_password_warning">Varování: Schránka je sdílena všemi aplikacemi. Pokud jsou do ní zkopírovány citlivé údaje, mohl by se k nim dostat další software.</string> <string name="allow_copy_password_warning">Varování: Schránka je sdílena všemi aplikacemi. Pokud jsou do ní zkopírovány citlivé údaje, mohl by se k nim dostat další software.</string>
@@ -253,50 +253,49 @@
<string name="education_create_database_summary">Založte svůj první soubor pro správu hesel.</string> <string name="education_create_database_summary">Založte svůj první soubor pro správu hesel.</string>
<string name="education_select_database_title">Otevřít existující databázi</string> <string name="education_select_database_title">Otevřít existující databázi</string>
<string name="education_select_database_summary">Otevřete svou dříve používanou databázi ze správce souborů a pokračujte v jejím používání.</string> <string name="education_select_database_summary">Otevřete svou dříve používanou databázi ze správce souborů a pokračujte v jejím používání.</string>
<string name="education_new_node_title">Přidejte položky do databáze</string> <string name="education_new_node_title">Přidejte záznamy do databáze</string>
<string name="education_new_node_summary">Položky pomáhají se správou vašich digitálních identit. <string name="education_new_node_summary">Záznamy pomáhají se správou Vašich digitálních identit.
\n \n
\nSkupiny (ekvivalent složek) organizují záznamy v databázi.</string> \nSkupiny (ekvivalent složek) organizují záznamy v databázi.</string>
<string name="education_search_title">Hledejte v položkách</string> <string name="education_search_title">Hledat v záznamech</string>
<string name="education_search_summary">Zadejte název, uživatelské jméno nebo jiné položky k nalezení svých hesel.</string> <string name="education_search_summary">Zadejte název, uživatelské jméno nebo jiné kolonky k nalezení svých hesel.</string>
<string name="education_entry_edit_title">Upravit položku</string> <string name="education_entry_edit_title">Upravit záznam</string>
<string name="education_entry_edit_summary">Přidejte ke své položce vlastní kolonky. Společná data mohou být sdílena mezi více různými kolonkami.</string> <string name="education_entry_edit_summary">Přidejte ke svému záznamu vlastní kolonky. Společná data mohou být sdílena mezi různými kolonkami záznamu odkazem.</string>
<string name="education_generate_password_title">Vytvořit silné heslo</string> <string name="education_generate_password_title">Vytvořit silné heslo</string>
<string name="education_generate_password_summary">Vygenerujte silné heslo pro svou položku, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.</string> <string name="education_generate_password_summary">Generujte silné heslo pro svůj záznam, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.</string>
<string name="education_entry_new_field_title">Přidat vlastní kolonky</string> <string name="education_entry_new_field_title">Přidat vlastní kolonky</string>
<string name="education_entry_new_field_summary">Registrovat další kolonku, zadat hodnotu a volitelně ji ochránit.</string> <string name="education_entry_new_field_summary">Registrovat další kolonku, zadat hodnotu a volitelně ji ochránit.</string>
<string name="education_unlock_title">Odemknout databázi</string> <string name="education_unlock_title">Odemknout databázi</string>
<string name="education_read_only_title">Ochraňte svou databázi před zápisem</string> <string name="education_read_only_title">Ochraňte svou databázi před zápisem</string>
<string name="education_read_only_summary">Změnit režim otevírání pro dané sezení. <string name="education_read_only_summary">Změnit režim otevírání pro dané sezení.
\n \n
\nV režimu \"pouze pro čtení\" zabráníte nechtěným změnám do databáze. \nV režimu \"pouze pro čtení\" zabráníte nechtěným změnám v databázi.
\nV režimu \"zápisu\" je možné přidávat, mazat nebo měnit všechny prvky podle libosti.</string> \nV režimu \"zápisu\" je možné přidávat, mazat nebo měnit všechny prvky podle libosti.</string>
<string name="education_field_copy_title">Zkopírujte kolonku</string> <string name="education_field_copy_title">Zkopírovat kolonku</string>
<string name="education_field_copy_summary">Zkopírované kolonky lze vkládat kam chcete <string name="education_field_copy_summary">Zkopírované kolonky lze vkládat podle libosti.
\n \n
\nK vyplňování formulářů použijte svou oblíbenou metodu.</string> \nK vyplňování formulářů použijte svou oblíbenou metodu.</string>
<string name="education_lock_title">Uzamkni databáze</string> <string name="education_lock_title">Uzamknout databázi</string>
<string name="education_lock_summary">Rychlé uzamkni databázi. Je možné nastavit, aby se databáze zamkla po určitém čase a také po zhasnutí obrazovky.</string> <string name="education_lock_summary">Rychle uzamknout databázi. Je možné nastavit, aby se databáze zamkla po určitém čase a také po zhasnutí obrazovky.</string>
<string name="education_sort_title">Řazení položek</string> <string name="education_sort_title">Řazení položek</string>
<string name="education_sort_summary">Vyberte řazení položek a skupin.</string> <string name="education_sort_summary">Vyberte řazení položek a skupin.</string>
<string name="education_donation_title">Zapojit se</string> <string name="education_donation_title">Zapojit se</string>
<string name="education_donation_summary">Zapojte se a pomozte zvýšit stabilitu, bezpečnost a přidávání dalších funkcí.</string> <string name="education_donation_summary">Zapojte se a pomozte zvýšit stabilitu, bezpečnost a doplnění dalších funkcí.</string>
<string name="html_text_ad_free">Na rozdíl od mnoha aplikací pro správu hesel, tato je &lt;strong&gt;bez reklam&lt;/strong&gt;", je "&lt;strong&gt;svobodný software pod copyleft licencí&lt;/strong&gt; a nesbírá žádné osobní údaje na svých serverech bez ohledu na to, jakou verzi používáte.</string> <string name="html_text_ad_free">Na rozdíl od mnoha aplikací pro správu hesel je tato &lt;strong&gt;bez reklam&lt;/strong&gt;, je \u0020&lt;strong&gt;svobodný software pod copyleft licencí&lt;/strong&gt; 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 &lt;strong&gt;vizuálnímu stylu&lt;/strong&gt; a hlavně pomůžete &lt;strong&gt;uskutečnění komunitních projektů.&lt;/strong&gt;</string> <string name="html_text_buy_pro">Zakoupením varianty \"pro\" získáte přístup k tomuto &lt;strong&gt;vizuálnímu stylu&lt;/strong&gt; a hlavně pomůžete &lt;strong&gt;uskutečnění komunitních projektů.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Tento &lt;strong&gt;vizuální styl&lt;/strong&gt; je k dispozici díky vaší štědrosti.</string> <string name="html_text_feature_generosity">Tento &lt;strong&gt;vizuální styl&lt;/strong&gt; je k dispozici díky vaší štědrosti.</string>
<string name="html_text_donation">Pro zajištění svobody nás všech a pokračování aktivity, počítáme s vaším &lt;strong&gt;přispěním.&lt;/strong&gt;</string> <string name="html_text_donation">Pro zajištění svobody nás všech a pokračování aktivity počítáme s Vaším &lt;strong&gt;přispěním.&lt;/strong&gt;</string>
<string name="html_text_dev_feature">Tato funkce je &lt;strong&gt;ve vývoji&lt;/strong&gt; a potřebuje váš &lt;strong&gt;příspěvek&lt;/strong&gt;, aby byla brzy k dispozici.</string> <string name="html_text_dev_feature">Tato funkce je &lt;strong&gt;ve vývoji&lt;/strong&gt; a potřebuje Váš &lt;strong&gt;příspěvek&lt;/strong&gt;, aby byla brzy k dispozici.</string>
<string name="html_text_dev_feature_buy_pro">Zakoupením &lt;strong&gt;pro&lt;/strong&gt; varianty,</string> <string name="html_text_dev_feature_buy_pro">Zakoupením &lt;strong&gt;pro&lt;/strong&gt; varianty,</string>
<string name="html_text_dev_feature_contibute">&lt;strong&gt;Zapojením se&lt;/strong&gt;,</string> <string name="html_text_dev_feature_contibute">&lt;strong&gt;Zapojením se&lt;/strong&gt;,</string>
<string name="html_text_dev_feature_encourage">povzbudíte vývojáře k přidává&lt;strong&gt;nových funkcí&lt;/strong&gt; a &lt;strong&gt;opravování chyb&lt;/strong&gt; dle vašich připomínek.</string> <string name="html_text_dev_feature_encourage">povzbudíte vývojáře k doplně&lt;strong&gt;nových funkcí&lt;/strong&gt; a &lt;strong&gt;opravám chyb&lt;/strong&gt; dle vašich připomínek.</string>
<string name="html_text_dev_feature_thanks">Mnohé díky za vašeispě.</string> <string name="html_text_dev_feature_thanks">Mockrát děkujeme za Vášíspěvek.</string>
<string name="html_text_dev_feature_work_hard">Tvrdě pracujeme na brzkém vydání této funkce.</string> <string name="html_text_dev_feature_work_hard">Tvrdě pracujeme na brzkém vydání této funkce.</string>
<string name="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string> <string name="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string>
<string name="download">Stáhnout</string> <string name="download">Stáhnout</string>
<string name="contribute">Zapojit se</string> <string name="contribute">Přispět</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Vzhled aplikace</string> <string name="style_choose_title">Vzhled aplikace</string>
<string name="style_choose_summary">Motiv vzhledu aplikace</string> <string name="style_choose_summary">Motiv vzhledu aplikace</string>
<string name="icon_pack_choose_title">Sada ikon</string> <string name="icon_pack_choose_title">Sada ikon</string>
@@ -305,16 +304,16 @@
<string name="keyboard_name">Magikeyboard</string> <string name="keyboard_name">Magikeyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string> <string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="keyboard_setting_label">Magikeyboard nastavení</string> <string name="keyboard_setting_label">Magikeyboard nastavení</string>
<string name="keyboard_entry_category">Položka</string> <string name="keyboard_entry_category">Záznam</string>
<string name="keyboard_entry_timeout_title">Časový limit</string> <string name="keyboard_entry_timeout_title">Časový limit</string>
<string name="keyboard_entry_timeout_summary">Doba uchování položky v Magikeyboardu</string> <string name="keyboard_entry_timeout_summary">Doba uchování položky v Magikeyboardu</string>
<string name="keyboard_notification_entry_title">Informace o oznámení</string> <string name="keyboard_notification_entry_title">Informace o oznámení</string>
<string name="keyboard_notification_entry_summary">Zobrazit oznámení, když je položka dostupná</string> <string name="keyboard_notification_entry_summary">Zobrazit oznámení, když je položka dostupná</string>
<string name="keyboard_notification_entry_content_title_text">Položka</string> <string name="keyboard_notification_entry_content_title_text">Záznam</string>
<string name="keyboard_notification_entry_content_title">%1$s dostupné v Magikeyboardu</string> <string name="keyboard_notification_entry_content_title">%1$s dostupné v Magikeyboardu</string>
<string name="keyboard_notification_entry_content_text">%1$s</string> <string name="keyboard_notification_entry_content_text">%1$s</string>
<string name="keyboard_notification_entry_clear_close_title">Vymazat při zavření</string> <string name="keyboard_notification_entry_clear_close_title">Vymazat při zavření</string>
<string name="keyboard_notification_entry_clear_close_summary">Zavři databázi při zavření oznámení</string> <string name="keyboard_notification_entry_clear_close_summary">Zavřít databázi při zavření oznámení</string>
<string name="keyboard_appearance_category">Vzhled</string> <string name="keyboard_appearance_category">Vzhled</string>
<string name="keyboard_theme_title">Vzhled klávesnice</string> <string name="keyboard_theme_title">Vzhled klávesnice</string>
<string name="keyboard_keys_category">Klávesy</string> <string name="keyboard_keys_category">Klávesy</string>
@@ -327,37 +326,37 @@
<string name="clear_clipboard_notification_title">Vymazat při ukončení</string> <string name="clear_clipboard_notification_title">Vymazat při ukončení</string>
<string name="clear_clipboard_notification_summary">Uzamknout databázi, jakmile trvání schránky vyprší nebo po uzavření oznámení</string> <string name="clear_clipboard_notification_summary">Uzamknout databázi, jakmile trvání schránky vyprší nebo po uzavření oznámení</string>
<string name="recycle_bin">Koš</string> <string name="recycle_bin">Koš</string>
<string name="keyboard_selection_entry_title">Výběr položky</string> <string name="keyboard_selection_entry_title">Výběr záznamu</string>
<string name="keyboard_selection_entry_summary">Při prohlížení záznamu ukázat na Magikeyboard pole položek</string> <string name="keyboard_selection_entry_summary">Při prohlížení záznamu ukázat na Magikeyboard kolonky</string>
<string name="delete_entered_password_title">Smazat heslo</string> <string name="delete_entered_password_title">Smazat heslo</string>
<string name="delete_entered_password_summary">Smaže heslo zadané po pokusu o připojení k databázi</string> <string name="delete_entered_password_summary">Smaže heslo zadané po pokusu o připojení k databázi</string>
<string name="content_description_open_file">Otevřít soubor</string> <string name="content_description_open_file">Otevřít soubor</string>
<string name="content_description_node_children">Potomci uzlu</string> <string name="content_description_node_children">Podřazené prvky uzlu</string>
<string name="content_description_add_node">Přidej uzel</string> <string name="content_description_add_node">Přidat uzel</string>
<string name="content_description_add_entry">Přidej záznam</string> <string name="content_description_add_entry">Přidat záznam</string>
<string name="content_description_add_group">Přidat skupinu</string> <string name="content_description_add_group">Přidat skupinu</string>
<string name="content_description_file_information">Informace o souboru</string> <string name="content_description_file_information">Informace o souboru</string>
<string name="content_description_password_checkbox">Checkbox hesla</string> <string name="content_description_password_checkbox">Checkbox hesla</string>
<string name="content_description_keyfile_checkbox">Checkbox souboru s klíčem</string> <string name="content_description_keyfile_checkbox">Checkbox souboru s klíčem</string>
<string name="content_description_repeat_toggle_password_visibility">Přepni ukázání hesla</string> <string name="content_description_repeat_toggle_password_visibility">Opakovat přepnutí viditelnosti hesla</string>
<string name="content_description_entry_icon">Ikona záznamu</string> <string name="content_description_entry_icon">Ikona záznamu</string>
<string name="entry_password_generator">Generátor hesel</string> <string name="entry_password_generator">Generátor hesel</string>
<string name="content_description_password_length">Délka hesla</string> <string name="content_description_password_length">Délka hesla</string>
<string name="entry_add_field">Přidej pole</string> <string name="entry_add_field">Přidat pole</string>
<string name="content_description_remove_field">Odeber pole</string> <string name="content_description_remove_field">Odebrat pole</string>
<string name="entry_UUID">UUID</string> <string name="entry_UUID">UUID</string>
<string name="error_move_entry_here">Sem záznam přesunout nelze.</string> <string name="error_move_entry_here">Sem záznam přesunout nelze.</string>
<string name="error_copy_entry_here">Sem záznam zkopírovat nelze.</string> <string name="error_copy_entry_here">Sem záznam zkopírovat nelze.</string>
<string name="list_groups_show_number_entries_title">Uk počet záznamů</string> <string name="list_groups_show_number_entries_title">Ukázat počet záznamů</string>
<string name="list_groups_show_number_entries_summary">Uk počet záznamů ve skupině</string> <string name="list_groups_show_number_entries_summary">Ukázat počet záznamů ve skupině</string>
<string name="content_description_background">Pozadí</string> <string name="content_description_background">Pozadí</string>
<string name="content_description_update_from_list">Aktualizovat</string> <string name="content_description_update_from_list">Aktualizovat</string>
<string name="content_description_keyboard_close_fields">Zavři kolonky</string> <string name="content_description_keyboard_close_fields">Zavřít pole</string>
<string name="error_create_database_file">Nelze vytvořit databázi s tímto heslem a klíčem ze souboru.</string> <string name="error_create_database_file">Nelze vytvořit databázi s tímto heslem a souborem klíče.</string>
<string name="menu_advanced_unlock_settings">Pokročilé odemčení</string> <string name="menu_advanced_unlock_settings">Rozšířené odemknutí</string>
<string name="biometric">Biometrika</string> <string name="biometric">Biometrika</string>
<string name="biometric_auto_open_prompt_title">Automaticky otevřít pobídku</string> <string name="biometric_auto_open_prompt_title">Automaticky otevřít pobídku</string>
<string name="biometric_auto_open_prompt_summary">Automaticky žádat pokročilé odemknutí, je-li databáze nastavena k jejímu použití</string> <string name="biometric_auto_open_prompt_summary">Automaticky žádat rozšířené odemknutí, je-li databáze nastavena k jejímu použití</string>
<string name="enable">Zapnout</string> <string name="enable">Zapnout</string>
<string name="disable">Vypnout</string> <string name="disable">Vypnout</string>
<string name="master_key">Hlavní klíč</string> <string name="master_key">Hlavní klíč</string>
@@ -373,7 +372,7 @@
<string name="entry_otp">OTP</string> <string name="entry_otp">OTP</string>
<string name="error_invalid_OTP">Neplatná OTP tajnost.</string> <string name="error_invalid_OTP">Neplatná OTP tajnost.</string>
<string name="error_disallow_no_credentials">Nejméně jeden přihlašovací údaj musí být zadán.</string> <string name="error_disallow_no_credentials">Nejméně jeden přihlašovací údaj musí být zadán.</string>
<string name="error_copy_group_here">Sem skupinu kopírovat nemůžete.</string> <string name="error_copy_group_here">Sem skupinu kopírovat nelze.</string>
<string name="error_otp_secret_key">Tajný klíč musí mít formát Base32.</string> <string name="error_otp_secret_key">Tajný klíč musí mít formát Base32.</string>
<string name="error_otp_counter">Čítač musít být mezi %1$d a %2$d.</string> <string name="error_otp_counter">Čítač musít být mezi %1$d a %2$d.</string>
<string name="error_otp_period">Interval musít být mezi %1$d a %2$d vteřinami.</string> <string name="error_otp_period">Interval musít být mezi %1$d a %2$d vteřinami.</string>
@@ -385,8 +384,8 @@
<string name="contains_duplicate_uuid">Databáze obsahuje duplikátní UUID.</string> <string name="contains_duplicate_uuid">Databáze obsahuje duplikátní UUID.</string>
<string name="contains_duplicate_uuid_procedure">Opravit chybu založením nového UUID pro duplikáty a pokračovat\?</string> <string name="contains_duplicate_uuid_procedure">Opravit chybu založením nového UUID pro duplikáty a pokračovat\?</string>
<string name="database_opened">Databáze otevřena</string> <string name="database_opened">Databáze otevřena</string>
<string name="clipboard_explanation_summary">Kopírujte pole záznamů pomocí schránky Vašeho zařízení</string> <string name="clipboard_explanation_summary">Kopírovat kolonky záznamů pomocí schránky svého zařízení</string>
<string name="advanced_unlock_explanation_summary">K snadnějšímu otevření databáze použijte pokročilé odemknutí</string> <string name="advanced_unlock_explanation_summary">K snadnějšímu otevření databáze použijte rozšířené odemknutí</string>
<string name="database_data_compression_title">Komprese dat</string> <string name="database_data_compression_title">Komprese dat</string>
<string name="database_data_compression_summary">Komprese dat snižuje velikost databáze</string> <string name="database_data_compression_summary">Komprese dat snižuje velikost databáze</string>
<string name="max_history_items_title">Maximální počet</string> <string name="max_history_items_title">Maximální počet</string>
@@ -414,20 +413,20 @@
<string name="recycle_bin_group_title">Skupina Koš</string> <string name="recycle_bin_group_title">Skupina Koš</string>
<string name="enable_auto_save_database_title">Uložit databázi automaticky</string> <string name="enable_auto_save_database_title">Uložit databázi automaticky</string>
<string name="enable_auto_save_database_summary">Uložit databázi po každé důležité akci (v režimu \"Zápis\")</string> <string name="enable_auto_save_database_summary">Uložit databázi po každé důležité akci (v režimu \"Zápis\")</string>
<string name="entry_attachments">ipojené soubory</string> <string name="entry_attachments">ílohy</string>
<string name="menu_restore_entry_history">Obnovit historii</string> <string name="menu_restore_entry_history">Obnovit historii</string>
<string name="menu_delete_entry_history">Smazat historii</string> <string name="menu_delete_entry_history">Smazat historii</string>
<string name="keyboard_auto_go_action_title">Akce auto-klávesy</string> <string name="keyboard_auto_go_action_title">Akce auto-klávesy</string>
<string name="keyboard_auto_go_action_summary">Akce klávesy \"Jít\" po stisknutí klávesy \"Položka\"</string> <string name="keyboard_auto_go_action_summary">Akce klávesy \"Jít\" po stisknutí klávesy \"Kolonka\"</string>
<string name="download_attachment">Stáhnout %1$s</string> <string name="download_attachment">Stáhnout %1$s</string>
<string name="download_initialization">Zahajuji…</string> <string name="download_initialization">Zahajuji…</string>
<string name="download_progression">Probíhá: %1$d%%</string> <string name="download_progression">Probíhá: %1$d%%</string>
<string name="download_finalization">Dokončuji…</string> <string name="download_finalization">Dokončuji…</string>
<string name="download_complete">Kompletní!</string> <string name="download_complete">Kompletní!</string>
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</string> <string name="hide_expired_entries_title">Skrýt propadlé záznamy</string>
<string name="hide_expired_entries_summary">Propadlé záznamy nejsou ukázány</string> <string name="hide_expired_entries_summary">Propadlé záznamy nebudou ukázány</string>
<string name="contact">Kontakt</string> <string name="contact">Kontakt</string>
<string name="contribution">Příspěvky</string> <string name="contribution">Příspě</string>
<string name="feedback">Feedback</string> <string name="feedback">Feedback</string>
<string name="auto_focus_search_title">Snadné hledání</string> <string name="auto_focus_search_title">Snadné hledání</string>
<string name="auto_focus_search_summary">Při otevření databáze žádat hledání</string> <string name="auto_focus_search_summary">Při otevření databáze žádat hledání</string>
@@ -437,23 +436,23 @@
<string name="remember_keyfile_locations_summary">Uchová informaci o tom, kde jsou uloženy soubory s klíči</string> <string name="remember_keyfile_locations_summary">Uchová informaci o tom, kde jsou uloženy soubory s klíči</string>
<string name="show_recent_files_title">Ukázat nedávné soubory</string> <string name="show_recent_files_title">Ukázat nedávné soubory</string>
<string name="show_recent_files_summary">Ukázat umístění nedávných databází</string> <string name="show_recent_files_summary">Ukázat umístění nedávných databází</string>
<string name="hide_broken_locations_title">Skrýt špatné odkazy na databáze</string> <string name="hide_broken_locations_title">Skrýt chybné odkazy na databáze</string>
<string name="hide_broken_locations_summary">Skrýt nesprávné odkazy v seznamu nedávných databází</string> <string name="hide_broken_locations_summary">Skrýt chybné odkazy v seznamu nedávných databází</string>
<string name="warning_database_read_only">Udělit právo zápisu pro uložení změn v databázi</string> <string name="warning_database_read_only">Udělit právo zápisu pro uložení změn v databázi</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft je &lt;strong&gt;open source&lt;/strong&gt; a &lt;strong&gt;bey reklam&lt;/strong&gt;. <string name="html_about_licence">KeePassDX © %1$d Kunzisoft je &lt;strong&gt;open source&lt;/strong&gt; a &lt;strong&gt;bey reklam&lt;/strong&gt;.
\nJe poskytován jak je, pod licencí &lt;strong&gt;GPLv3&lt;/strong&gt;, bez jakékoli záruky.</string> \nJe poskytován jak je, pod licencí &lt;strong&gt;GPLv3&lt;/strong&gt;, bez jakékoli záruky.</string>
<string name="html_about_contribution">Abychom si &lt;strong&gt;udrželi svoji svobodu&lt;/strong&gt;, &lt;strong&gt;opravili chyby&lt;/strong&gt;,&lt;strong&gt;doplnili funkce&lt;/strong&gt; a &lt;strong&gt;byli vždy aktivní&lt;/strong&gt;, počítáme s Vaším &lt;strong&gt;přispěním&lt;/strong&gt;.</string> <string name="html_about_contribution">Abychom si &lt;strong&gt;udrželi svoji svobodu&lt;/strong&gt;, &lt;strong&gt;opravili chyby&lt;/strong&gt;,&lt;strong&gt;doplnili funkce&lt;/strong&gt; a &lt;strong&gt;byli vždy aktivní&lt;/strong&gt;, počítáme s Vaším &lt;strong&gt;přispěním&lt;/strong&gt;.</string>
<string name="error_create_database">Nedaří se vytvořit soubor s databází.</string> <string name="error_create_database">Nepodařilo se vytvořit soubor databáze.</string>
<string name="entry_add_attachment">Přidat přílohu</string> <string name="entry_add_attachment">Přidat přílohu</string>
<string name="discard">Zahodit</string> <string name="discard">Zavrhnout</string>
<string name="discard_changes">Zahodit změny\?</string> <string name="discard_changes">Zavrhnout změny\?</string>
<string name="validate">Ověřit</string> <string name="validate">Zkontrolovat</string>
<string name="education_setup_OTP_summary">Nastavit správu One-Time hesla (HOTP / TOTP) pro založení tokenu požadovaného pro dvoufázové ověření (2FA).</string> <string name="education_setup_OTP_summary">Nastavit správu One-Time hesla (HOTP / TOTP) pro založení tokenu požadovaného pro dvoufázové ověření (2FA).</string>
<string name="education_setup_OTP_title">Nastavit OTP</string> <string name="education_setup_OTP_title">Nastavit OTP</string>
<string name="autofill_auto_search_summary">Automaticky navrhnout výsledky hledání z webové domény nebo ID aplikace</string> <string name="autofill_auto_search_summary">Automaticky navrhnout výsledky hledání z webové domény nebo ID aplikace</string>
<string name="autofill_auto_search_title">Samočinné hledání</string> <string name="autofill_auto_search_title">Samočinné hledání</string>
<string name="lock_database_show_button_summary">Ukáže tlačítko zámku v uživatelském rozhraní</string> <string name="lock_database_show_button_summary">Zobrazí tlačítko zámku v uživatelském rozhraní</string>
<string name="lock_database_show_button_title">Ukázat tlačítko zámku</string> <string name="lock_database_show_button_title">Zobrazit tlačítko zámku</string>
<string name="autofill_preference_title">Nastavení samovyplnění</string> <string name="autofill_preference_title">Nastavení samovyplnění</string>
<string name="warning_database_link_revoked">Přístup k souboru zrušenému správcem souborů</string> <string name="warning_database_link_revoked">Přístup k souboru zrušenému správcem souborů</string>
<string name="error_label_exists">Tento štítek již existuje.</string> <string name="error_label_exists">Tento štítek již existuje.</string>
@@ -470,14 +469,14 @@
<string name="subdomain_search_title">Hledat v subdoméně</string> <string name="subdomain_search_title">Hledat v subdoméně</string>
<string name="error_string_type">Tento text se s požadovanou položkou neshoduje.</string> <string name="error_string_type">Tento text se s požadovanou položkou neshoduje.</string>
<string name="content_description_add_item">Přidat položku</string> <string name="content_description_add_item">Přidat položku</string>
<string name="keyboard_previous_fill_in_summary">Automaticky přepnout na předchozí klávesnici po provedení Akce auto-klávesy</string> <string name="keyboard_previous_fill_in_summary">Automaticky přepnout na předchozí klávesnici po provedení \"Akce auto-klávesy\"</string>
<string name="keyboard_previous_fill_in_title">Akce auto-klávesy</string> <string name="keyboard_previous_fill_in_title">Akce auto-klávesy</string>
<string name="keyboard_previous_database_credentials_summary">Automaticky přepnout zpět na předchozí klávesnici na obrazovce ověřovacích údajů databáze</string> <string name="keyboard_previous_database_credentials_summary">Automaticky přepnout zpět na předchozí klávesnici na obrazovce ověřovacích údajů databáze</string>
<string name="keyboard_previous_database_credentials_title">Obrazovka ověřovacích údajů databáze</string> <string name="keyboard_previous_database_credentials_title">Obrazovka ověřovacích údajů databáze</string>
<string name="keyboard_change">Přepnout klávesnici</string> <string name="keyboard_change">Přepnout klávesnici</string>
<string name="upload_attachment">Nahrát %1$s</string> <string name="upload_attachment">Nahrát %1$s</string>
<string name="content_description_credentials_information">Info o údajích</string> <string name="content_description_credentials_information">Informace o údajích</string>
<string name="warning_file_too_big">Databáze KeePassu předpokládá uchovávat jen malé pomocné sobory (např. PGP soubory). <string name="warning_file_too_big">Databáze KeePassu předpokládá uchovávat jen malé pomocné sobory (např. PGP soubory s klíči).
\n \n
\nVaše databáze by se mohla značně zvětšit a tímto nahráním tak ztratit na výkonnosti.</string> \nVaše databáze by se mohla značně zvětšit a tímto nahráním tak ztratit na výkonnosti.</string>
<string name="warning_replace_file">Nahráním tohoto souboru nahradíte existující soubor.</string> <string name="warning_replace_file">Nahráním tohoto souboru nahradíte existující soubor.</string>
@@ -498,41 +497,53 @@
<string name="autofill_ask_to_save_data_title">Zeptat se před uložením</string> <string name="autofill_ask_to_save_data_title">Zeptat se před uložením</string>
<string name="autofill_save_search_info_summary">Pokuste se uložit údaje hledání, když manuálně vybíráte položku</string> <string name="autofill_save_search_info_summary">Pokuste se uložit údaje hledání, když manuálně vybíráte položku</string>
<string name="autofill_save_search_info_title">Uložit info hledání</string> <string name="autofill_save_search_info_title">Uložit info hledání</string>
<string name="autofill_close_database_summary">Zavřít databázi po samodoplnění polí</string> <string name="autofill_close_database_summary">Zavřít databázi po samovyplnění polí</string>
<string name="autofill_close_database_title">Zavřít databázi</string> <string name="autofill_close_database_title">Zavřít databázi</string>
<string name="keyboard_previous_lock_summary">Po uzamknutí databáze automaticky přepnout zpět na předchozí klávesnici</string> <string name="keyboard_previous_lock_summary">Po uzamknutí databáze automaticky přepnout zpět na předchozí klávesnici</string>
<string name="keyboard_previous_lock_title">Uzamknout databázi</string> <string name="keyboard_previous_lock_title">Uzamknout databázi</string>
<string name="keyboard_save_search_info_summary">Pokuste se uložit sdílené info, když manuálné vybíráte položku</string> <string name="keyboard_save_search_info_summary">Pokuste se uložit sdílené info, když manuálné vybíráte položku</string>
<string name="keyboard_save_search_info_title">Uložit sdílené info</string> <string name="keyboard_save_search_info_title">Uložit sdílené info</string>
<string name="notification">Oznámení</string> <string name="notification">Oznámení</string>
<string name="crypto_object_not_initialized">Krypto objekt nelze načíst.</string>
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string> <string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string>
<string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string> <string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string>
<string name="warning_empty_recycle_bin">Trvale odstranit všechny položky z koše\?</string> <string name="warning_empty_recycle_bin">Trvale odstranit všechny uzly z koše\?</string>
<string name="registration_mode">Režim registrace</string> <string name="registration_mode">Režim registrace</string>
<string name="save_mode">Režim ukládání</string> <string name="save_mode">Režim ukládání</string>
<string name="search_mode">Režim vyhledávání</string> <string name="search_mode">Režim vyhledávání</string>
<string name="error_field_name_already_exists">Jméno položky již existuje.</string> <string name="error_field_name_already_exists">Jméno kolonky již existuje.</string>
<string name="error_registration_read_only">Uložení nové položky v režimu databáze pouze pro čtení není povoleno</string> <string name="error_registration_read_only">Uložení nové položky v režimu databáze pouze pro čtení není povoleno</string>
<string name="enter">Enter</string> <string name="enter">Enter</string>
<string name="backspace">Backspace</string> <string name="backspace">Backspace</string>
<string name="select_entry">Vybrat záznam</string> <string name="select_entry">Vybrat záznam</string>
<string name="back_to_previous_keyboard">Zpět na předchozí klávesnici</string> <string name="back_to_previous_keyboard">Zpět na předchozí klávesnici</string>
<string name="custom_fields">Vlastní položky</string> <string name="custom_fields">Vlastní kolonky</string>
<string name="advanced_unlock_delete_all_key_warning">Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí\?</string> <string name="advanced_unlock_delete_all_key_warning">Smazat všechny šifrovací klíče související s rozpoznáním rozšířeného odemknutí\?</string>
<string name="device_credential_unlock_enable_summary">Dovolí pro otevření databáze použít heslo Vašeho zařízení</string> <string name="device_credential_unlock_enable_summary">Dovolí pro otevření databáze použít heslo Vašeho zařízení</string>
<string name="device_credential_unlock_enable_title">Odemknutí heslem zařízení</string> <string name="device_credential_unlock_enable_title">Odemknutí heslem zařízení</string>
<string name="device_credential">Heslo zařízení</string> <string name="device_credential">Heslo zařízení</string>
<string name="credential_before_click_advanced_unlock_button">Zadejte heslo a klikněte na tlačítko \"Pokročilé odemknutí\".</string> <string name="credential_before_click_advanced_unlock_button">Zadejte heslo a klikněte na tlačítko \"Rozšířené odemknutí\".</string>
<string name="advanced_unlock_prompt_not_initialized">Nelze inicializovat pobídku pro pokročilé odemknutí.</string> <string name="advanced_unlock_prompt_not_initialized">Nelze inicializovat pobídku pro rozšířené odemknutí.</string>
<string name="advanced_unlock_scanning_error">Chyba při pokročilém odemknutí: %1$s</string> <string name="advanced_unlock_scanning_error">Chyba při rozšířeném odemknutí: %1$s</string>
<string name="advanced_unlock_not_recognized">Otisk pro pokročilé odemknutí nebyl rozpoznán</string> <string name="advanced_unlock_not_recognized">Otisk pro rozšířené odemknutí nebyl rozpoznán</string>
<string name="advanced_unlock_invalid_key">Nelze načíst klíč pokročilého odemknutí. Prosím, smažte jej a opakujte proces rozpoznání uzamknutí.</string> <string name="advanced_unlock_invalid_key">Nelze načíst klíč rozšířeného odemknutí. Prosím, smažte jej a opakujte proces rozpoznání odemknutí.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Načíst důvěrný údaj pomocí dat pokročilého odemknutí</string> <string name="advanced_unlock_prompt_extract_credential_message">Načíst důvěrný údaj pomocí dat rozšířeného odemknutí</string>
<string name="advanced_unlock_prompt_extract_credential_title">Otevřít databázi pomocí rozpoznání pokročilého odemknutí</string> <string name="advanced_unlock_prompt_extract_credential_title">Otevřít databázi pomocí rozpoznání rozšířeného odemknutí</string>
<string name="advanced_unlock_prompt_store_credential_message">Varování: Pokud použijete rozpoznání pokročilého odemknutí, musíte si i nadále pamatovat hlavní heslo.</string> <string name="advanced_unlock_prompt_store_credential_message">Varování: Pokud použijete rozpoznání rozšířeného odemknutí, musíte si i nadále pamatovat hlavní heslo.</string>
<string name="advanced_unlock_prompt_store_credential_title">Rozpoznání pokročilého odemknutí</string> <string name="advanced_unlock_prompt_store_credential_title">Rozpoznání rozšířeného odemknutí</string>
<string name="open_advanced_unlock_prompt_store_credential">Pro uložení důvěrných údajů otevřete pobídku pokročilého odemknutí</string> <string name="open_advanced_unlock_prompt_store_credential">Pro uložení důvěrných údajů otevřete pobídku rozšířeného odemknutí</string>
<string name="open_advanced_unlock_prompt_unlock_database">Pro odemknutí databáze otevřete pobídku pokročilého odemknutí</string> <string name="open_advanced_unlock_prompt_unlock_database">Pro odemknutí databáze otevřete pobídku rozšířeného odemknutí</string>
<string name="menu_keystore_remove_key">Smazat klíč pokročilého odemknutí</string> <string name="menu_keystore_remove_key">Smazat klíč rozšířeného odemknutí</string>
<string name="education_advanced_unlock_title">Rozšířené odemknutí databáze</string>
<string name="advanced_unlock_timeout">Časový limit rozšířeného odemknutí</string>
<string name="temp_advanced_unlock_timeout_summary">Trvání použití rozšířeného odemknutí než bude obsah téhož smazán</string>
<string name="temp_advanced_unlock_enable_summary">Za účelem rozšířeného odemknutí neukládat žádný šifrovaný obsah</string>
<string name="temp_advanced_unlock_enable_title">Přechodné rozšířené odemknutí</string>
<string name="advanced_unlock_tap_delete">Pro odstranění klíčů rozšířeného odemknutí klepnout</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="education_advanced_unlock_summary">Abyste rychle odemknuli databázi, propojte své heslo s naskenovanou biometrikou nebo údaji zámku zařízení.</string>
<string name="temp_advanced_unlock_timeout_title">Vypršení pokročilého odemknutí</string>
<string name="content">Obsah</string>
<string name="error_rebuild_list">Seznam nelze řádně sestavit.</string>
<string name="error_database_uri_null">URI databáze nelze načíst.</string>
</resources> </resources>

View File

@@ -295,7 +295,6 @@
<string name="contribute">Bidrag</string> <string name="contribute">Bidrag</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Tema</string> <string name="style_choose_title">Tema</string>
<string name="style_choose_summary">Tema, der bruges i programmet</string> <string name="style_choose_summary">Tema, der bruges i programmet</string>
<string name="icon_pack_choose_title">Ikonpakke</string> <string name="icon_pack_choose_title">Ikonpakke</string>
@@ -492,7 +491,6 @@
<string name="database_data_remove_unlinked_attachments_summary">Fjerner vedhæftede filer indeholdt i databasen, men ikke knyttet til en post</string> <string name="database_data_remove_unlinked_attachments_summary">Fjerner vedhæftede filer indeholdt i databasen, men ikke knyttet til en post</string>
<string name="database_data_remove_unlinked_attachments_title">Fjern ikke-sammenkædede data</string> <string name="database_data_remove_unlinked_attachments_title">Fjern ikke-sammenkædede data</string>
<string name="data">Data</string> <string name="data">Data</string>
<string name="crypto_object_not_initialized">Kryptoobjektet kunne ikke hentes.</string>
<string name="biometric_security_update_required">Biometrisk sikkerhedsopdatering påkrævet.</string> <string name="biometric_security_update_required">Biometrisk sikkerhedsopdatering påkrævet.</string>
<string name="warning_empty_keyfile_explanation">Indholdet af nøglefilen bør aldrig ændres og bør i bedste fald indeholde tilfældigt genererede data.</string> <string name="warning_empty_keyfile_explanation">Indholdet af nøglefilen bør aldrig ændres og bør i bedste fald indeholde tilfældigt genererede data.</string>
<string name="warning_empty_keyfile">Det anbefales ikke at tilføje en tom nøglefil.</string> <string name="warning_empty_keyfile">Det anbefales ikke at tilføje en tom nøglefil.</string>

View File

@@ -118,8 +118,8 @@
<string name="never">Nie</string> <string name="never">Nie</string>
<string name="no_results">Keine Suchergebnisse</string> <string name="no_results">Keine Suchergebnisse</string>
<string name="no_url_handler">Bitte einen Webbrowser installieren, um diese URL zu öffnen.</string> <string name="no_url_handler">Bitte einen Webbrowser installieren, um diese URL zu öffnen.</string>
<string name="omit_backup_search_title">Papierkorb/Sicherung nicht durchsuchen</string> <string name="omit_backup_search_title">Recycle bin und Backup nicht durchsuchen</string>
<string name="omit_backup_search_summary">Die Gruppen „Sicherung“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt</string> <string name="omit_backup_search_summary">Die Gruppen „Backup“ und „Recycle bin“ werden bei der Suche nicht berücksichtigt</string>
<string name="auto_focus_search_title">Schnellsuche</string> <string name="auto_focus_search_title">Schnellsuche</string>
<string name="auto_focus_search_summary">Beim Öffnen einer Datenbank eine Suche veranlassen</string> <string name="auto_focus_search_summary">Beim Öffnen einer Datenbank eine Suche veranlassen</string>
<string name="progress_create">Neue Datenbank anlegen </string> <string name="progress_create">Neue Datenbank anlegen </string>
@@ -218,7 +218,7 @@
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen</string> <string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen</string>
<string name="clipboard">Zwischenablage</string> <string name="clipboard">Zwischenablage</string>
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string> <string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
<string name="biometric_delete_all_key_summary">Alle mit der biometrischen Erkennung verbundenen Verschlüsselungsschlüssel löschen.</string> <string name="biometric_delete_all_key_summary">Lösche alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen</string>
<string name="unavailable_feature_version">Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.</string> <string name="unavailable_feature_version">Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.</string>
<string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string> <string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string>
<string name="recycle_bin_title">Papierkorb-Nutzung</string> <string name="recycle_bin_title">Papierkorb-Nutzung</string>
@@ -231,7 +231,7 @@
<string name="database_description_title">Datenbankbeschreibung</string> <string name="database_description_title">Datenbankbeschreibung</string>
<string name="database_version_title">Datenbankversion</string> <string name="database_version_title">Datenbankversion</string>
<string name="text_appearance">Text</string> <string name="text_appearance">Text</string>
<string name="application_appearance">App</string> <string name="application_appearance">Interface</string>
<string name="other">Andere</string> <string name="other">Andere</string>
<string name="keyboard">Tastatur</string> <string name="keyboard">Tastatur</string>
<string name="magic_keyboard_title">Magikeyboard</string> <string name="magic_keyboard_title">Magikeyboard</string>
@@ -267,7 +267,7 @@
<string name="education_donation_title">Mitmachen</string> <string name="education_donation_title">Mitmachen</string>
<string name="education_donation_summary">Mithelfen, um Stabilität und Sicherheit zu verbessern und weitere Funktionen zu ermöglichen.</string> <string name="education_donation_summary">Mithelfen, um Stabilität und Sicherheit zu verbessern und weitere Funktionen zu ermöglichen.</string>
<string name="html_text_ad_free">Anders als viele andere Passwortmanager ist dieser &lt;strong&gt;werbefrei&lt;/strong&gt;, &lt;strong&gt;quelloffen&lt;/strong&gt; und unter einer &lt;strong&gt;Copyleft-Lizenz&lt;/strong&gt;. Es werden keine persönlichen Daten gesammelt, in welcher Form auch immer, unabhängig von der verwendeten Version (kostenlos oder Pro).</string> <string name="html_text_ad_free">Anders als viele andere Passwortmanager ist dieser &lt;strong&gt;werbefrei&lt;/strong&gt;, &lt;strong&gt;quelloffen&lt;/strong&gt; und unter einer &lt;strong&gt;Copyleft-Lizenz&lt;/strong&gt;. Es werden keine persönlichen Daten gesammelt, in welcher Form auch immer, unabhängig von der verwendeten Version (kostenlos oder Pro).</string>
<string name="html_text_buy_pro">Mit dem Kauf der Pro-Version erhält man Zugang zu diesem &lt;strong&gt;visuellen Stil&lt;/strong&gt; und unterstützt insbesondere &lt;strong&gt;die Umsetzung gemeinschaftlicher Projektarbeiten.&lt;/strong&gt;</string> <string name="html_text_buy_pro">Mit dem Kauf der Pro-Version erhalten Sie Zugriff auf diesen &lt;strong&gt;visuellen Stil&lt;/strong&gt; und unterstützen insbesondere &lt;strong&gt;die Umsetzung gemeinschaftlicher Projekte.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Dieser &lt;strong&gt;visuelle Stil&lt;/strong&gt; wurde wegen Ihrer Großzügigkeit freigeschaltet.</string> <string name="html_text_feature_generosity">Dieser &lt;strong&gt;visuelle Stil&lt;/strong&gt; wurde wegen Ihrer Großzügigkeit freigeschaltet.</string>
<string name="html_text_donation">Um unsere Freiheit zu bewahren und immer aktiv zu bleiben, zählen wir auf Ihren &lt;strong&gt;Beitrag.&lt;/strong&gt;</string> <string name="html_text_donation">Um unsere Freiheit zu bewahren und immer aktiv zu bleiben, zählen wir auf Ihren &lt;strong&gt;Beitrag.&lt;/strong&gt;</string>
<string name="html_text_dev_feature">Diese Funktion ist &lt;strong&gt;in Entwicklung&lt;/strong&gt; und erfordert &lt;strong&gt;Ihren Beitrag&lt;/strong&gt;, um bald verfügbar zu sein.</string> <string name="html_text_dev_feature">Diese Funktion ist &lt;strong&gt;in Entwicklung&lt;/strong&gt; und erfordert &lt;strong&gt;Ihren Beitrag&lt;/strong&gt;, um bald verfügbar zu sein.</string>
@@ -276,12 +276,11 @@
<string name="html_text_dev_feature_encourage">Sie ermutigen die Entwickler:innen, &lt;strong&gt;neue Funktionen&lt;/strong&gt; einzuführen und gemäß Ihren Anmerkungen &lt;strong&gt;Fehler zu beheben&lt;/strong&gt;.</string> <string name="html_text_dev_feature_encourage">Sie ermutigen die Entwickler:innen, &lt;strong&gt;neue Funktionen&lt;/strong&gt; einzuführen und gemäß Ihren Anmerkungen &lt;strong&gt;Fehler zu beheben&lt;/strong&gt;.</string>
<string name="html_text_dev_feature_thanks">Vielen Dank für Ihre Unterstützung.</string> <string name="html_text_dev_feature_thanks">Vielen Dank für Ihre Unterstützung.</string>
<string name="html_text_dev_feature_work_hard">Wir bemühen uns, diese Funktion bald zu veröffentlichen.</string> <string name="html_text_dev_feature_work_hard">Wir bemühen uns, diese Funktion bald zu veröffentlichen.</string>
<string name="html_text_dev_feature_upgrade">Vergessen Sie nicht, Ihre Anwendung aktuell zu halten, indem Sie neue Versionen installieren.</string> <string name="html_text_dev_feature_upgrade">Denken Sie daran, Ihre App auf dem neuesten Stand zu halten, indem Sie neue Versionen installieren.</string>
<string name="download">Download</string> <string name="download">Download</string>
<string name="contribute">Unterstützen</string> <string name="contribute">Unterstützen</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="icon_pack_choose_title">Symbolpaket</string> <string name="icon_pack_choose_title">Symbolpaket</string>
<string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string> <string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string>
<string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string> <string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string>
@@ -303,7 +302,7 @@
<string name="education_read_only_summary">Den Öffnungsmodus für die Sitzung ändern. <string name="education_read_only_summary">Den Öffnungsmodus für die Sitzung ändern.
\n \n
\n„Schreibgeschützt“ verhindert unbeabsichtigte Änderungen an der Datenbank. \n„Schreibgeschützt“ verhindert unbeabsichtigte Änderungen an der Datenbank.
\nMit „Änderbar“ können alle Elemente hinzugefügt, gelöscht oder geändert werden.</string> \nMit „Änderbar“ können Sie alle Elemente nach Belieben hinzufügen, löschen oder ändern.</string>
<string name="edit_entry">Eintrag bearbeiten</string> <string name="edit_entry">Eintrag bearbeiten</string>
<string name="error_load_database">Datenbank kann nicht geladen werden.</string> <string name="error_load_database">Datenbank kann nicht geladen werden.</string>
<string name="error_load_database_KDF_memory">Laden des Schlüssels fehlgeschlagen. Bitte versuchen, die „Speicherplatznutzung“ von KDF zu verringern.</string> <string name="error_load_database_KDF_memory">Laden des Schlüssels fehlgeschlagen. Bitte versuchen, die „Speicherplatznutzung“ von KDF zu verringern.</string>
@@ -337,7 +336,7 @@
<string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string> <string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string>
<string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string> <string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string>
<string name="hide_broken_locations_summary">Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden</string> <string name="hide_broken_locations_summary">Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden</string>
<string name="do_not_kill_app">Nicht die Anwendung beenden …</string> <string name="do_not_kill_app">App nicht beenden …</string>
<string name="lock_database_back_root_summary">Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird</string> <string name="lock_database_back_root_summary">Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird</string>
<string name="clear_clipboard_notification_title">Beim Schließen löschen</string> <string name="clear_clipboard_notification_title">Beim Schließen löschen</string>
<string name="recycle_bin">Papierkorb</string> <string name="recycle_bin">Papierkorb</string>
@@ -374,8 +373,8 @@
<string name="biometric">Biometrisch</string> <string name="biometric">Biometrisch</string>
<string name="enable">Aktivieren</string> <string name="enable">Aktivieren</string>
<string name="disable">Deaktivieren</string> <string name="disable">Deaktivieren</string>
<string name="biometric_auto_open_prompt_title">Biometrische Abfrage automatisch öffnen</string> <string name="biometric_auto_open_prompt_title">Abfrage automatisch öffnen</string>
<string name="biometric_auto_open_prompt_summary">Automatisch nach Biometrie fragen, wenn die Datenbank dafür eingerichtet ist</string> <string name="biometric_auto_open_prompt_summary">Automatisch nach der erweiterten Entsperrung fragen, wenn die Datenbank dafür eingerichtet ist</string>
<string name="master_key">Hauptschlüssel</string> <string name="master_key">Hauptschlüssel</string>
<string name="security">Sicherheit</string> <string name="security">Sicherheit</string>
<string name="entry_history">Verlauf</string> <string name="entry_history">Verlauf</string>
@@ -501,7 +500,7 @@
<string name="upload_attachment">%1$s hochladen</string> <string name="upload_attachment">%1$s hochladen</string>
<string name="education_add_attachment_title">Anhang hinzufügen</string> <string name="education_add_attachment_title">Anhang hinzufügen</string>
<string name="database_data_remove_unlinked_attachments_summary">Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind</string> <string name="database_data_remove_unlinked_attachments_summary">Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind</string>
<string name="warning_sure_add_file">Die Datei trotzdem hinzufügen\?</string> <string name="warning_sure_add_file">Soll die Datei trotzdem hinzugefügt werden\?</string>
<string name="show_uuid_summary">Zeigt die mit einem Eintrag verknüpfte UUID an</string> <string name="show_uuid_summary">Zeigt die mit einem Eintrag verknüpfte UUID an</string>
<string name="show_uuid_title">UUID anzeigen</string> <string name="show_uuid_title">UUID anzeigen</string>
<string name="autofill_read_only_save">Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.</string> <string name="autofill_read_only_save">Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.</string>
@@ -509,7 +508,6 @@
<string name="keyboard_previous_lock_summary">Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank</string> <string name="keyboard_previous_lock_summary">Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank</string>
<string name="keyboard_previous_lock_title">Datenbank sperren</string> <string name="keyboard_previous_lock_title">Datenbank sperren</string>
<string name="notification">Benachrichtigung</string> <string name="notification">Benachrichtigung</string>
<string name="crypto_object_not_initialized">Kryptoobjekt kann nicht abgerufen werden.</string>
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string> <string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
<string name="configure_biometric">Es sind keine biometrischen oder Geräteanmeldeinformationen registriert.</string> <string name="configure_biometric">Es sind keine biometrischen oder Geräteanmeldeinformationen registriert.</string>
<string name="registration_mode">Registrierungsmodus</string> <string name="registration_mode">Registrierungsmodus</string>
@@ -529,5 +527,34 @@
<string name="open_advanced_unlock_prompt_store_credential">Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen</string> <string name="open_advanced_unlock_prompt_store_credential">Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen</string>
<string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string> <string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string>
<string name="menu_keystore_remove_key">Löschen des Schlüssels zum erweiterten Entsperren</string> <string name="menu_keystore_remove_key">Löschen des Schlüssels zum erweiterten Entsperren</string>
<string name="advanced_unlock_prompt_store_credential_title">Fortschrittliche Entsperrerkennung</string> <string name="advanced_unlock_prompt_store_credential_title">Erweiterte Entsperrerkennung</string>
<string name="education_advanced_unlock_summary">Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.</string>
<string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string>
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string>
<string name="temp_advanced_unlock_timeout_summary">Dauer der erweiterten Entsperrung bevor sein Inhalt gelöscht wird</string>
<string name="temp_advanced_unlock_timeout_title">Verfall der erweiterten Entsperrung</string>
<string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen</string>
<string name="temp_advanced_unlock_enable_title">Temporäre erweiterte Entsperrung</string>
<string name="device_credential_unlock_enable_summary">Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
<string name="advanced_unlock_tap_delete">Drücken um erweiterte Entsperrschlüssel zu löschen</string>
<string name="content">Inhalt</string>
<string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit erweiterter Entsperrerkennung</string>
<string name="enter">Eingabetaste</string>
<string name="backspace">Rücktaste</string>
<string name="select_entry">Wähle Eintrag</string>
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
<string name="custom_fields">Benutzerdefinierte Felder</string>
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?</string>
<string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string>
<string name="device_credential">Geräteanmeldedaten</string>
<string name="credential_before_click_advanced_unlock_button">Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.</string>
<string name="advanced_unlock_prompt_not_initialized">Initialisieren des erweitertes Entsperren Dialogs fehlgeschlagen.</string>
<string name="advanced_unlock_scanning_error">Erweitertes Entsperren Fehler: %1$s</string>
<string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string>
<string name="advanced_unlock_invalid_key">Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_rebuild_list">Die Liste kann nicht ordnungsgemäß neu erstellt werden.</string>
<string name="error_database_uri_null">Datenbank-URI kann nicht abgerufen werden.</string>
</resources> </resources>

View File

@@ -262,7 +262,6 @@
<string name="contribute">Συνεισφορά</string> <string name="contribute">Συνεισφορά</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Θέμα Εφαρμογής</string> <string name="style_choose_title">Θέμα Εφαρμογής</string>
<string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string> <string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string>
<string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string> <string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string>
@@ -504,7 +503,6 @@
<string name="keyboard_save_search_info_summary">Προσπαθήστε να αποθηκεύσετε κοινόχρηστες πληροφορίες όταν κάνετε μια χειροκίνητη επιλογή καταχώρησης</string> <string name="keyboard_save_search_info_summary">Προσπαθήστε να αποθηκεύσετε κοινόχρηστες πληροφορίες όταν κάνετε μια χειροκίνητη επιλογή καταχώρησης</string>
<string name="keyboard_save_search_info_title">Αποθήκευση κοινόχρηστων πληροφοριών</string> <string name="keyboard_save_search_info_title">Αποθήκευση κοινόχρηστων πληροφοριών</string>
<string name="notification">Ειδοποίηση</string> <string name="notification">Ειδοποίηση</string>
<string name="crypto_object_not_initialized">Δεν είναι δυνατή η ανάκτηση κρυπτογραφικού αντικειμένου.</string>
<string name="biometric_security_update_required">Απαιτείται ενημέρωση βιομετρικής ασφάλειας.</string> <string name="biometric_security_update_required">Απαιτείται ενημέρωση βιομετρικής ασφάλειας.</string>
<string name="configure_biometric">Κανένα πιστοποιητικό βιομετρίας ή συσκευής δεν είναι εγγεγραμμένο.</string> <string name="configure_biometric">Κανένα πιστοποιητικό βιομετρίας ή συσκευής δεν είναι εγγεγραμμένο.</string>
<string name="warning_empty_recycle_bin">Να διαγραφούν οριστικά όλοι οι κόμβοι από τον κάδο ανακύκλωσης;</string> <string name="warning_empty_recycle_bin">Να διαγραφούν οριστικά όλοι οι κόμβοι από τον κάδο ανακύκλωσης;</string>
@@ -543,4 +541,15 @@
<string name="temp_advanced_unlock_timeout_title">Λήξη προηγμένου ξεκλειδώματος</string> <string name="temp_advanced_unlock_timeout_title">Λήξη προηγμένου ξεκλειδώματος</string>
<string name="advanced_unlock_tap_delete">Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος</string> <string name="advanced_unlock_tap_delete">Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος</string>
<string name="content">Περιεχόμενα</string> <string name="content">Περιεχόμενα</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_rebuild_list">Δεν είναι δυνατή η σωστή αναδημιουργία της λίστας.</string>
<string name="error_database_uri_null">Δεν είναι δυνατή η ανάκτηση του URI βάσης δεδομένων.</string>
<string name="autofill_inline_suggestions_keyboard">Προστέθηκαν προτάσεις αυτόματης συμπλήρωσης.</string>
<string name="autofill_inline_suggestions_summary">Απόπειρα εμφάνισης προτάσεων αυτόματης συμπλήρωσης απευθείας από ένα συμβατό πληκτρολόγιο</string>
<string name="autofill_inline_suggestions_title">Προτάσεις στην ίδια γραμμή</string>
<string name="warning_database_revoked">Πρόσβαση στο αρχείο που ανακλήθηκε από το διαχειριστή αρχείων, κλείστε τη βάση δεδομένων και ανοίξτε το ξανά από τη θέση του.</string>
<string name="warning_database_info_changed_options">Αντικαταστήστε τις εξωτερικές τροποποιήσεις αποθηκεύοντας τη βάση δεδομένων ή φορτώστε την ξανά με τις πιο πρόσφατες αλλαγές.</string>
<string name="warning_database_info_changed">Οι πληροφορίες που περιέχονται στο αρχείο της βάσης δεδομένων σας έχουν τροποποιηθεί εκτός της εφαρμογής.</string>
<string name="menu_reload_database">Επαναφόρτωση βάσης δεδομένων</string>
</resources> </resources>

View File

@@ -19,9 +19,9 @@
Spanish translation by José I. Paños. Updated by David García-Abad (23-09-2013) Spanish translation by José I. Paños. Updated by David García-Abad (23-09-2013)
--><resources> --><resources>
<string name="feedback">Commentario</string> <string name="feedback">Comentarios</string>
<string name="homepage">Página de inicio</string> <string name="homepage">Página de inicio</string>
<string name="about_description">Implementación para Android del gestor de contraseñas KeePass</string> <string name="about_description">Implementación en Android del gestor de contraseñas KeePass</string>
<string name="accept">Aceptar</string> <string name="accept">Aceptar</string>
<string name="add_entry">Añadir entrada</string> <string name="add_entry">Añadir entrada</string>
<string name="add_group">Añadir grupo</string> <string name="add_group">Añadir grupo</string>
@@ -31,30 +31,30 @@
<string name="application">Aplicación</string> <string name="application">Aplicación</string>
<string name="menu_app_settings">Configuración de la aplicación</string> <string name="menu_app_settings">Configuración de la aplicación</string>
<string name="brackets">Paréntesis</string> <string name="brackets">Paréntesis</string>
<string name="file_manager_install_description">Explora ficheros con OpenIntents File Manager</string> <string name="file_manager_install_description">Se requiere un administrador de archivos que acepte la acciones de intención ACTION_CREATE_DOCUMENT y ACTION_OPEN_DOCUMENT para crear, abrir y guardar los archivos de la base de datos.</string>
<string name="clipboard_cleared">Portapapeles limpiado</string> <string name="clipboard_cleared">Portapapeles limpiado</string>
<string name="clipboard_timeout">Portapapeles caducado</string> <string name="clipboard_timeout">Portapapeles caducado</string>
<string name="clipboard_timeout_summary">Duración del almacenamiento en el portapapeles (si lo admite el dispositivo)</string> <string name="clipboard_timeout_summary">Duración del almacenamiento en el portapapeles (si lo admite su dispositivo)</string>
<string name="select_to_copy">Seleccionar para copiar %1$s en el portapapeles</string> <string name="select_to_copy">Seleccionar para copiar %1$s en el portapapeles</string>
<string name="retrieving_db_key">Creando clave de la base de datos…</string> <string name="retrieving_db_key">Recuperando la clave de la base de datos…</string>
<string name="database">Base de datos</string> <string name="database">Base de datos</string>
<string name="decrypting_db">Descifrando el contenido de la base de datos…</string> <string name="decrypting_db">Descifrando el contenido de la base de datos…</string>
<string name="default_checkbox">Utilice como base de datos por defecto</string> <string name="default_checkbox">Utilizar como base de datos por defecto</string>
<string name="digits">Dígitos</string> <string name="digits">Dígitos</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft es \u0020de &lt;strong&gt;código abierto&lt;/strong&gt; y &lt;strong&gt;sin publicidad&lt;/strong&gt;. <string name="html_about_licence">KeePassDX © %1$d Kunzisoft es de &lt;strong&gt;código abierto&lt;/strong&gt; y &lt;strong&gt;sin publicidad&lt;/strong&gt;.
\nSe proporciona tal cual, bajo licencia &lt;strong&gt;GPLv3&lt;/strong&gt;, sin ninguna garantía.</string> \nSe proporciona tal cual, bajo licencia &lt;strong&gt;GPLv3&lt;/strong&gt;, sin ninguna garantía.</string>
<string name="select_database_file">Abrir base de datos existente</string> <string name="select_database_file">Abrir base de datos existente</string>
<string name="entry_accessed">Acceso</string> <string name="entry_accessed">Accedido</string>
<string name="entry_cancel">Cancelar</string> <string name="entry_cancel">Cancelar</string>
<string name="entry_notes">Notas</string> <string name="entry_notes">Notas</string>
<string name="entry_confpassword">Confirmar contraseña</string> <string name="entry_confpassword">Confirmar contraseña</string>
<string name="entry_created">Creación</string> <string name="entry_created">Creado</string>
<string name="entry_expires">Caducidad</string> <string name="entry_expires">Caduca</string>
<string name="entry_keyfile">Archivo de clave</string> <string name="entry_keyfile">Archivo de clave</string>
<string name="entry_modified">Modificación</string> <string name="entry_modified">Modificada</string>
<string name="entry_password">Contraseña</string> <string name="entry_password">Contraseña</string>
<string name="save">Guardar</string> <string name="save">Guardar</string>
<string name="entry_title">Nombre</string> <string name="entry_title">Título</string>
<string name="entry_url">URL</string> <string name="entry_url">URL</string>
<string name="entry_user_name">Nombre de usuario</string> <string name="entry_user_name">Nombre de usuario</string>
<string name="error_arc4">No se admite el cifrador de flujo Arcfour.</string> <string name="error_arc4">No se admite el cifrador de flujo Arcfour.</string>
@@ -64,11 +64,11 @@
<string name="error_invalid_path">Asegúrese de que la ruta sea correcta.</string> <string name="error_invalid_path">Asegúrese de que la ruta sea correcta.</string>
<string name="error_no_name">Proporcione un nombre.</string> <string name="error_no_name">Proporcione un nombre.</string>
<string name="error_nokeyfile">Seleccione un archivo de clave.</string> <string name="error_nokeyfile">Seleccione un archivo de clave.</string>
<string name="error_out_of_memory">No queda memoria para cargar toda la base de datos.</string> <string name="error_out_of_memory">No hay memoria para cargar toda la base de datos.</string>
<string name="error_pass_gen_type">Debe seleccionar al menos un tipo de generación de contraseñas.</string> <string name="error_pass_gen_type">Debe seleccionar al menos un tipo de generación de contraseñas.</string>
<string name="error_pass_match">Las contraseñas no coinciden.</string> <string name="error_pass_match">Las contraseñas no coinciden.</string>
<string name="error_rounds_too_large">Pasadas demasiado grande. Establecido a 2147483648.</string> <string name="error_rounds_too_large">Pasadas demasiado grande. Establecido a 2147483648.</string>
<string name="error_wrong_length">Proporcione un número entero positivo en el campo «Longitud».</string> <string name="error_wrong_length">Introduzca un número entero positivo en el campo \"Longitud\".</string>
<string name="file_browser">Explorador de archivos</string> <string name="file_browser">Explorador de archivos</string>
<string name="generate_password">Generar contraseña</string> <string name="generate_password">Generar contraseña</string>
<string name="hint_conf_pass">Confirmar contraseña</string> <string name="hint_conf_pass">Confirmar contraseña</string>
@@ -78,7 +78,7 @@
<string name="hint_length">Longitud</string> <string name="hint_length">Longitud</string>
<string name="password">Contraseña</string> <string name="password">Contraseña</string>
<string name="hint_pass">Contraseña</string> <string name="hint_pass">Contraseña</string>
<string name="invalid_credentials">Contraseña o archivo de clave no válido.</string> <string name="invalid_credentials">No se pudieron leer las credenciales.</string>
<string name="invalid_db_sig">No se pudo reconocer el formato de la base de datos.</string> <string name="invalid_db_sig">No se pudo reconocer el formato de la base de datos.</string>
<string name="length">Longitud</string> <string name="length">Longitud</string>
<string name="list_size_title">Tamaño de los elementos de la lista</string> <string name="list_size_title">Tamaño de los elementos de la lista</string>
@@ -103,7 +103,7 @@
<string name="minus">Menos</string> <string name="minus">Menos</string>
<string name="never">Nunca</string> <string name="never">Nunca</string>
<string name="no_results">Sin resultado de búsqueda</string> <string name="no_results">Sin resultado de búsqueda</string>
<string name="no_url_handler">Instale un navegador web para abrir este URL.</string> <string name="no_url_handler">Instale un navegador web para abrir esta URL.</string>
<string name="omit_backup_search_title">No buscar en las entradas de respaldo</string> <string name="omit_backup_search_title">No buscar en las entradas de respaldo</string>
<string name="omit_backup_search_summary">Omite los grupos «Respaldo» y «Papelera de reciclaje» de los resultados de búsqueda</string> <string name="omit_backup_search_summary">Omite los grupos «Respaldo» y «Papelera de reciclaje» de los resultados de búsqueda</string>
<string name="progress_create">Creando nueva base de datos…</string> <string name="progress_create">Creando nueva base de datos…</string>
@@ -124,7 +124,9 @@
<string name="unsupported_db_version">No se admite esta versión de la base de datos.</string> <string name="unsupported_db_version">No se admite esta versión de la base de datos.</string>
<string name="uppercase">Mayúsculas</string> <string name="uppercase">Mayúsculas</string>
<string name="version_label">Versión %1$s</string> <string name="version_label">Versión %1$s</string>
<string name="education_unlock_summary">Introduzca una contraseña y/o un archivo de clave para desbloquear su base de datos.</string> <string name="education_unlock_summary">Introduzca la contraseña y/o el archivo clave para desbloquear su base de datos.
\n
\nHaga una copia de seguridad de su archivo de base de datos en un lugar seguro después de cada cambio.</string>
<string-array name="timeout_options"> <string-array name="timeout_options">
<item>5 segundos</item> <item>5 segundos</item>
<item>10 segundos</item> <item>10 segundos</item>
@@ -146,28 +148,28 @@
<string name="extended_ASCII">ASCII extendido</string> <string name="extended_ASCII">ASCII extendido</string>
<string name="allow">Permitir</string> <string name="allow">Permitir</string>
<string name="clipboard_error_title">Error del portapapeles</string> <string name="clipboard_error_title">Error del portapapeles</string>
<string name="clipboard_error">Algunos dispositivos Android tienen un error en la implementación del portapapeles que provoca fallos al copiar desde las aplicaciones.</string> <string name="clipboard_error">Algunos dispositivos no permiten que las aplicaciones usen el portapapeles.</string>
<string name="clipboard_error_clear">Falló la limpieza del portapapeles</string> <string name="clipboard_error_clear">No se pudo limpiar el portapapeles</string>
<string name="error_string_key">Cada cadena debe tener un nombre de campo.</string> <string name="error_string_key">Cada cadena debe tener un nombre de campo.</string>
<string name="error_autofill_enable_service">El servicio de autocompletado no se ha podido habilitar.</string> <string name="error_autofill_enable_service">El servicio de autocompletado no se ha podido habilitar.</string>
<string name="field_name">Nombre del campo</string> <string name="field_name">Nombre del campo</string>
<string name="field_value">Valor del campo</string> <string name="field_value">Valor del campo</string>
<string name="file_not_found_content">No se pudo encontrar el archivo. Intente volver a abrirlo en el explorador de archivos.</string> <string name="file_not_found_content">No se pudo encontrar el archivo. Intente volver a abrirlo en el explorador de archivos.</string>
<string name="invalid_algorithm">El algoritmo es incorrecto.</string> <string name="invalid_algorithm">Algoritmo incorrecto.</string>
<string name="keyfile_is_empty">El archivo de clave está vacío.</string> <string name="keyfile_is_empty">El archivo de clave está vacío.</string>
<string name="copy_field">Copia de %1$s</string> <string name="copy_field">Copia de %1$s</string>
<string name="menu_form_filling_settings">Llenado de formulario</string> <string name="menu_form_filling_settings">Llenado de formulario</string>
<string name="protection">Protección</string> <string name="protection">Protección</string>
<string name="read_only">Protegida contra escritura</string> <string name="read_only">Protegida contra escritura</string>
<string name="read_only_warning">KeePassDX necesita permiso de escritura para modificar la base de datos.</string> <string name="read_only_warning">Dependiendo del administrador de archivos, puede que KeePassDX no permita escribir en su almacenamiento.</string>
<string name="encryption_explanation">Algoritmo de cifrado utilizado en todos los datos.</string> <string name="encryption_explanation">Algoritmo de cifrado utilizado para todos los datos.</string>
<string name="kdf_explanation">Para generar la clave del algoritmo de cifrado, la clave maestra se transforma mediante una función de derivación de claves con una sal aleatoria.</string> <string name="kdf_explanation">Para generar la clave del algoritmo de cifrado, la clave maestra se transforma mediante una función de derivación de claves con una sal aleatoria.</string>
<string name="memory_usage">Uso de memoria</string> <string name="memory_usage">Uso de memoria</string>
<string name="memory_usage_explanation">Cantidad de memoria (en bytes) que usará la función de derivación de clave.</string> <string name="memory_usage_explanation">Cantidad de memoria (en bytes) que usará la función de derivación de clave.</string>
<string name="parallelism">Paralelismo</string> <string name="parallelism">Paralelismo</string>
<string name="parallelism_explanation">Grado de paralelismo (p. ej. número de hilos) usados por la función de derivación de clave.</string> <string name="parallelism_explanation">Grado de paralelismo (p. ej. número de hilos) usados por la función de derivación de clave.</string>
<string name="sort_menu">Ordenar</string> <string name="sort_menu">Ordenar</string>
<string name="sort_ascending">Inferiores primero ↓</string> <string name="sort_ascending">Más bajo primero ↓</string>
<string name="sort_groups_before">Agrupar antes</string> <string name="sort_groups_before">Agrupar antes</string>
<string name="sort_recycle_bin_bottom">Papelera debajo</string> <string name="sort_recycle_bin_bottom">Papelera debajo</string>
<string name="sort_title">Título</string> <string name="sort_title">Título</string>
@@ -177,8 +179,8 @@
<string name="sort_last_access_time">Acceso</string> <string name="sort_last_access_time">Acceso</string>
<string name="search_results">Resultados de búsqueda</string> <string name="search_results">Resultados de búsqueda</string>
<string name="warning">Atención</string> <string name="warning">Atención</string>
<string name="entry_not_found">Datos de entrada no encontrados.</string> <string name="entry_not_found">No se pudieron encontrar los datos de entrada.</string>
<string name="warning_password_encoding">Evite emplear en la base de datos caracteres que no pertenezcan al formato de codificación del texto (los caracteres no reconocidos se convierten a la misma letra).</string> <string name="warning_password_encoding">Evite los caracteres de la contraseña fuera del formato de codificación de texto en el archivo de la base de datos (los caracteres no reconocidos se convierten a la misma letra).</string>
<string name="warning_empty_password">¿Continuar sin la protección de desbloqueo de contraseña\?</string> <string name="warning_empty_password">¿Continuar sin la protección de desbloqueo de contraseña\?</string>
<string name="warning_no_encryption_key">¿Continuar sin clave de cifrado\?</string> <string name="warning_no_encryption_key">¿Continuar sin clave de cifrado\?</string>
<string name="encrypted_value_stored">Contraseña cifrada almacenada</string> <string name="encrypted_value_stored">Contraseña cifrada almacenada</string>
@@ -197,25 +199,25 @@
<string name="list_password_generator_options_summary">Establecer los caracteres permitidos del generador de contraseñas</string> <string name="list_password_generator_options_summary">Establecer los caracteres permitidos del generador de contraseñas</string>
<string name="clipboard">Portapapeles</string> <string name="clipboard">Portapapeles</string>
<string name="clipboard_notifications_title">Notificaciones del portapapeles</string> <string name="clipboard_notifications_title">Notificaciones del portapapeles</string>
<string name="clipboard_notifications_summary">Mostrar notificaciones del portapapeles para copiar campos al examinar una entrada</string> <string name="clipboard_notifications_summary">Mostrar las notificaciones del portapapeles para copiar campos al examinar una entrada</string>
<string name="lock">Bloquear</string> <string name="lock">Bloquear</string>
<string name="lock_database_screen_off_title">Bloqueo de pantalla</string> <string name="lock_database_screen_off_title">Bloqueo de pantalla</string>
<string name="lock_database_screen_off_summary">Bloquear la base de datos cuando la pantalla esté apagada</string> <string name="lock_database_screen_off_summary">Bloquear la base de datos cuando la pantalla esté apagada</string>
<string name="advanced_unlock">Desbloqueo avanzado</string> <string name="advanced_unlock">Desbloqueo avanzado</string>
<string name="biometric_unlock_enable_title">Desbloqueo biométrico</string> <string name="biometric_unlock_enable_title">Desbloqueo biométrico</string>
<string name="biometric_unlock_enable_summary">Le permite escanear su huella dactilar u otro dato biométrico para abrir la base de datos</string> <string name="biometric_unlock_enable_summary">Le permite escanear sus datos biométricos para abrir la base de datos</string>
<string name="biometric_delete_all_key_title">Eliminar claves de cifrado</string> <string name="biometric_delete_all_key_title">Eliminar claves de cifrado</string>
<string name="biometric_delete_all_key_summary">Eliminar todas las claves de cifrado relacionadas con el reconocimiento biométrico</string> <string name="biometric_delete_all_key_summary">Eliminar todas las claves de cifrado relacionadas con el reconocimiento de desbloqueo avanzado</string>
<string name="unavailable_feature_text">No se pudo iniciar esta funcionalidad.</string> <string name="unavailable_feature_text">No se pudo iniciar esta funcionalidad.</string>
<string name="unavailable_feature_version">La versión de Android del dispositivo es %1$s, pero es necesaria la %2$s o posterior.</string> <string name="unavailable_feature_version">El dispositivo funciona con Android %1$s, pero necesita %2$s o posterior.</string>
<string name="unavailable_feature_hardware">No se encontró el hardware correspondiente.</string> <string name="unavailable_feature_hardware">No se pudo encontrar el hardware correspondiente.</string>
<string name="file_name">Nombre del archivo</string> <string name="file_name">Nombre del archivo</string>
<string name="path">Ruta</string> <string name="path">Ruta</string>
<string name="assign_master_key">Asignar una clave maestra</string> <string name="assign_master_key">Asignar una clave maestra</string>
<string name="create_keepass_file">Crear base de datos nueva</string> <string name="create_keepass_file">Crear una base de datos nueva</string>
<string name="recycle_bin_title">Uso de la papelera de reciclaje</string> <string name="recycle_bin_title">Uso de la papelera de reciclaje</string>
<string name="recycle_bin_summary">Mueve los grupos y las entradas al grupo \"Papelera de reciclaje\" antes de eliminarlos</string> <string name="recycle_bin_summary">Mueve los grupos y las entradas al grupo \"Papelera de reciclaje\" antes de eliminarlos</string>
<string name="monospace_font_fields_enable_title">Fuente de los campos</string> <string name="monospace_font_fields_enable_title">Tipografía del campo</string>
<string name="monospace_font_fields_enable_summary">Cambiar la fuente de los campos para una mejor visibilidad del carácter</string> <string name="monospace_font_fields_enable_summary">Cambiar la fuente de los campos para una mejor visibilidad del carácter</string>
<string name="allow_copy_password_title">Portapapeles de confianza</string> <string name="allow_copy_password_title">Portapapeles de confianza</string>
<string name="allow_copy_password_summary">Permitir copiar la contraseña de entrada y los campos protegidos al portapapeles</string> <string name="allow_copy_password_summary">Permitir copiar la contraseña de entrada y los campos protegidos al portapapeles</string>
@@ -223,11 +225,11 @@
<string name="database_description_title">Descripción de la base de datos</string> <string name="database_description_title">Descripción de la base de datos</string>
<string name="database_version_title">Versión de la base de datos</string> <string name="database_version_title">Versión de la base de datos</string>
<string name="text_appearance">Texto</string> <string name="text_appearance">Texto</string>
<string name="application_appearance">Aplicación</string> <string name="application_appearance">Interfaz</string>
<string name="other">Otro</string> <string name="other">Otro</string>
<string name="keyboard">Teclado</string> <string name="keyboard">Teclado</string>
<string name="magic_keyboard_title">Teclado mágico</string> <string name="magic_keyboard_title">Teclado mágico</string>
<string name="magic_keyboard_explanation_summary">Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente.</string> <string name="magic_keyboard_explanation_summary">Active un teclado personalizado que llene sus contraseñas y todos los campos de identidad fácilmente</string>
<string name="reset_education_screens_title">Restablecer sugerencias didácticas</string> <string name="reset_education_screens_title">Restablecer sugerencias didácticas</string>
<string name="reset_education_screens_summary">Mostrar de nuevo toda la información didáctica</string> <string name="reset_education_screens_summary">Mostrar de nuevo toda la información didáctica</string>
<string name="reset_education_screens_text">Se restablecieron las sugerencias didácticas</string> <string name="reset_education_screens_text">Se restablecieron las sugerencias didácticas</string>
@@ -259,29 +261,28 @@
<string name="education_donation_title">Participar</string> <string name="education_donation_title">Participar</string>
<string name="education_donation_summary">Participe para aumentar la estabilidad, la seguridad y agregar más funciones.</string> <string name="education_donation_summary">Participe para aumentar la estabilidad, la seguridad y agregar más funciones.</string>
<string name="html_text_ad_free">A diferencia de muchas aplicaciones de administración de contraseñas, esta es &lt;strong&gt;sin publicidad&lt;/strong&gt;, &lt;strong&gt;software libre con copyleft&lt;/strong&gt; y no recopila datos personales en sus servidores, sin importar la versión que use.</string> <string name="html_text_ad_free">A diferencia de muchas aplicaciones de administración de contraseñas, esta es &lt;strong&gt;sin publicidad&lt;/strong&gt;, &lt;strong&gt;software libre con copyleft&lt;/strong&gt; y no recopila datos personales en sus servidores, sin importar la versión que use.</string>
<string name="html_text_buy_pro">Al comprar la versión pro, tendrá acceso a &lt;strong&gt;la característica visual &lt;/strong&gt;y usted ayudará especialmente a &lt;strong&gt;la realización de proyectos comunitarios.&lt;/strong&gt;</string> <string name="html_text_buy_pro">Al comprar la versión pro, tendrá acceso al &lt;strong&gt;estilo visual &lt;/strong&gt;y ayudará especialmente a &lt;strong&gt;la realización de proyectos comunitarios.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Esta &lt;strong&gt;característica visual &lt;/strong&gt;está disponible gracias a tu generosidad.</string> <string name="html_text_feature_generosity">Este &lt;strong&gt;estilo visual &lt;/strong&gt;está disponible gracias a tu generosidad.</string>
<string name="html_text_donation">Para mantener nuestra libertad y estar siempre vigente, contamos con tu &lt;strong&gt;contribución.&lt;/strong&gt;</string> <string name="html_text_donation">Para mantener nuestra libertad y estar siempre vigente, contamos con tu &lt;strong&gt;contribución.&lt;/strong&gt;</string>
<string name="html_text_dev_feature">Esta función está &lt;strong&gt;en desarrollo&lt;/strong&gt; y requiere de tu &lt;strong&gt;contribución&lt;/strong&gt; para estar disponible dentro de poco.</string> <string name="html_text_dev_feature">Esta función está &lt;strong&gt;en desarrollo&lt;/strong&gt; y requiere de tu &lt;strong&gt;contribución&lt;/strong&gt; para estar disponible dentro de poco.</string>
<string name="html_text_dev_feature_buy_pro">Al comprar la versión &lt;strong&gt;pro&lt;/strong&gt;,</string> <string name="html_text_dev_feature_buy_pro">Al comprar la versión &lt;strong&gt;pro&lt;/strong&gt;,</string>
<string name="html_text_dev_feature_contibute">Al &lt;strong&gt;contribuir&lt;/strong&gt;,</string> <string name="html_text_dev_feature_contibute">Al &lt;strong&gt;contribuir&lt;/strong&gt;,</string>
<string name="html_text_dev_feature_encourage">usted alienta a los desarrolladores a crear &lt;strong&gt;nuevas funciones&lt;/strong&gt; y a &lt;strong&gt;errores de configuración&lt;/strong&gt; de acuerdo con tus comentarios.</string> <string name="html_text_dev_feature_encourage">anima a los desarrolladores a crear &lt;strong&gt;nuevas funciones&lt;/strong&gt; y a &lt;strong&gt;corregir errores&lt;/strong&gt; de acuerdo con sus comentarios.</string>
<string name="html_text_dev_feature_thanks">Muchas gracias por tu contribución.</string> <string name="html_text_dev_feature_thanks">Muchas gracias por tu contribución.</string>
<string name="html_text_dev_feature_work_hard">Estamos trabajando duro para lanzar esta característica rápidamente.</string> <string name="html_text_dev_feature_work_hard">Estamos trabajando duro para lanzar esta característica rápidamente.</string>
<string name="html_text_dev_feature_upgrade">No olvide mantener su aplicación actualizada.</string> <string name="html_text_dev_feature_upgrade">Recuerde mantener su aplicación actualizada instalando nuevas versiones.</string>
<string name="download">Descargar</string> <string name="download">Descargar</string>
<string name="contribute">Contribuir</string> <string name="contribute">Contribuir</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES-KDF</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string> <string name="style_choose_title">Tema de la aplicación</string>
<string name="style_choose_title">Tema de aplicación</string>
<string name="style_choose_summary">Tema utilizado en la aplicación</string> <string name="style_choose_summary">Tema utilizado en la aplicación</string>
<string name="icon_pack_choose_title">Seleccione un paquete de iconos</string> <string name="icon_pack_choose_title">Seleccione un paquete de iconos</string>
<string name="icon_pack_choose_summary">Cambiar el paquete de iconos en la aplicación</string> <string name="icon_pack_choose_summary">Cambiar el paquete de iconos en la aplicación</string>
<string name="edit_entry">Editar entrada</string> <string name="edit_entry">Editar entrada</string>
<string name="error_load_database">No se pudo cargar la base de datos.</string> <string name="error_load_database">No se pudo cargar la base de datos.</string>
<string name="error_load_database_KDF_memory">No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.</string> <string name="error_load_database_KDF_memory">No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.</string>
<string name="error_move_folder_in_itself">No se puede mover un grupo dentro de sí mismo.</string> <string name="error_move_folder_in_itself">No puede mover un grupo dentro de sí mismo.</string>
<string name="list_entries_show_username_title">Enseña nombres de usuario</string> <string name="list_entries_show_username_title">Enseña nombres de usuario</string>
<string name="list_entries_show_username_summary">Enseña nombres de usuador en las listras de entradas</string> <string name="list_entries_show_username_summary">Enseña nombres de usuador en las listras de entradas</string>
<string name="menu_copy">Copiar</string> <string name="menu_copy">Copiar</string>
@@ -292,13 +293,13 @@
<string name="menu_open_file_read_and_write">Modificable</string> <string name="menu_open_file_read_and_write">Modificable</string>
<string name="build_label">Compilación %1$s</string> <string name="build_label">Compilación %1$s</string>
<string name="clipboard_warning">Si la eliminación del cortapapeles falla, elimine su historial manualmente.</string> <string name="clipboard_warning">Si la eliminación del cortapapeles falla, elimine su historial manualmente.</string>
<string name="allow_copy_password_warning">ATENCIÓN: todas las aplicaciones comparten el portapapeles. Si copia datos confidenciales, otros programas podrían recuperarlos.</string> <string name="allow_copy_password_warning">Advertencia: El portapapeles es compartido por todas las aplicaciones. Si se copian datos sensibles, otros programas pueden recuperarlos.</string>
<string name="allow_no_password_title">No permitir claves maestras</string> <string name="allow_no_password_title">No permitir claves maestras</string>
<string name="allow_no_password_summary">Activar el botón «Abrir» si no se selecciona ninguna credencial</string> <string name="allow_no_password_summary">Permite pulsar el botón \"Abrir\" si no se seleccionan credenciales</string>
<string name="enable_education_screens_title">Pantallas didácticas</string> <string name="enable_education_screens_title">Sugerencias educativas</string>
<string name="enable_education_screens_summary">Resaltar los elementos para conocer cómo funciona la aplicación</string> <string name="enable_education_screens_summary">Destacar elementos para aprender cómo funciona la aplicación</string>
<string name="enable_read_only_title">Protegida contra escritura</string> <string name="enable_read_only_title">Protegida contra escritura</string>
<string name="enable_read_only_summary">Abrir su base de datos protegida contra escritura de manera predeterminada</string> <string name="enable_read_only_summary">Abrir la base de datos de solo lectura por defecto</string>
<string name="education_read_only_title">Proteja la base de datos contra escritura</string> <string name="education_read_only_title">Proteja la base de datos contra escritura</string>
<string name="keyboard_name">Teclado mágico</string> <string name="keyboard_name">Teclado mágico</string>
<string name="keyboard_label">Teclado mágico (KeePassDX)</string> <string name="keyboard_label">Teclado mágico (KeePassDX)</string>
@@ -316,56 +317,56 @@
<string name="keyboard_appearance_category">Apariencia</string> <string name="keyboard_appearance_category">Apariencia</string>
<string name="keyboard_theme_title">Tema del teclado</string> <string name="keyboard_theme_title">Tema del teclado</string>
<string name="keyboard_keys_category">Teclas</string> <string name="keyboard_keys_category">Teclas</string>
<string name="keyboard_key_vibrate_title">Vibrar al pulsar</string> <string name="keyboard_key_vibrate_title">Vibrar al pulsar tecla</string>
<string name="keyboard_key_sound_title">Emitir sonido al pulsar</string> <string name="keyboard_key_sound_title">Sonar al pulsar tecla</string>
<string name="selection_mode">Modo de selección</string> <string name="selection_mode">Modo de selección</string>
<string name="do_not_kill_app">No cierre la aplicación…</string> <string name="do_not_kill_app">No cierre la aplicación…</string>
<string name="lock_database_back_root_summary">Bloquear la base de datos cuando se pulsa el botón Atrás en la pantalla inicial</string> <string name="lock_database_back_root_summary">Bloquear la base de datos cuando el usuario pulse el botón atrás en la pantalla inicial</string>
<string name="clear_clipboard_notification_title">Vaciar al cerrar</string> <string name="clear_clipboard_notification_title">Vaciar al cerrar</string>
<string name="clear_clipboard_notification_summary">Bloquear la base de datos cuando la duración del portapapeles expire o la notificación sea cerrada</string> <string name="clear_clipboard_notification_summary">Bloquear la base de datos cuando expire la duración del portapapeles o cuando se cierre la notificación después de empezar a utilizarla</string>
<string name="recycle_bin">Papelera de reciclaje</string> <string name="recycle_bin">Papelera de reciclaje</string>
<string name="keyboard_selection_entry_title">Selección de entrada</string> <string name="keyboard_selection_entry_title">Selección de entrada</string>
<string name="keyboard_selection_entry_summary">Mostrar campos de entrada en el Teclado mágico al ver una entrada</string> <string name="keyboard_selection_entry_summary">Mostrar campos de entrada en el Teclado mágico al ver una entrada</string>
<string name="delete_entered_password_title">Eliminar contraseña</string> <string name="delete_entered_password_title">Eliminar contraseña</string>
<string name="delete_entered_password_summary">Elimina la contraseña proporcionada tras un intento de conexión</string> <string name="delete_entered_password_summary">Elimina la contraseña introducida tras un intento de conexión a una bse de datos</string>
<string name="content_description_open_file">Abrir archivo</string> <string name="content_description_open_file">Abrir archivo</string>
<string name="content_description_node_children">Elementos secundarios del nodo</string> <string name="content_description_node_children">Nodos hijo</string>
<string name="content_description_add_node">Añadir nodo</string> <string name="content_description_add_node">Añadir nodo</string>
<string name="content_description_add_entry">Añadir entrada</string> <string name="content_description_add_entry">Añadir entrada</string>
<string name="content_description_add_group">Añadir grupo</string> <string name="content_description_add_group">Añadir grupo</string>
<string name="content_description_file_information">Información de archivo</string> <string name="content_description_file_information">Información del archivo</string>
<string name="content_description_password_checkbox">Casilla de contraseña</string> <string name="content_description_password_checkbox">Casilla de verificación de la contraseña</string>
<string name="content_description_keyfile_checkbox">Casilla de archivo de clave</string> <string name="content_description_keyfile_checkbox">Casilla de verificación del archivo de clave</string>
<string name="content_description_entry_icon">Icono de entrada</string> <string name="content_description_entry_icon">Icono de entrada</string>
<string name="entry_password_generator">Generador de contraseñas</string> <string name="entry_password_generator">Generador de contraseñas</string>
<string name="content_description_password_length">Longitud de contraseña</string> <string name="content_description_password_length">Longitud de contraseña</string>
<string name="entry_add_field">Añadir campo</string> <string name="entry_add_field">Añadir campo</string>
<string name="content_description_remove_field">Eliminar campo</string> <string name="content_description_remove_field">Eliminar el campo</string>
<string name="entry_UUID">UUID</string> <string name="entry_UUID">UUID</string>
<string name="error_move_entry_here">No se puede mover una entrada aquí.</string> <string name="error_move_entry_here">No puede mover una entrada aquí.</string>
<string name="error_copy_entry_here">No se puede copiar una entrada aquí.</string> <string name="error_copy_entry_here">No puede copiar una entrada aquí.</string>
<string name="list_groups_show_number_entries_title">Mostrar cantidad de entradas</string> <string name="list_groups_show_number_entries_title">Mostrar el número de entradas</string>
<string name="list_groups_show_number_entries_summary">Mostrar la cantidad de entradas en un grupo</string> <string name="list_groups_show_number_entries_summary">Mostrar el número de entradas en un grupo</string>
<string name="content_description_background">Fondo</string> <string name="content_description_background">Fondo</string>
<string name="content_description_update_from_list">Actualizar</string> <string name="content_description_update_from_list">Actualizar</string>
<string name="content_description_keyboard_close_fields">Cerrar campos</string> <string name="content_description_keyboard_close_fields">Cerrar campos</string>
<string name="error_create_database_file">No se puede crear la base de datos con esta contraseña y este archivo de clave.</string> <string name="error_create_database_file">No se puede crear la base de datos con esta contraseña y este archivo de clave.</string>
<string name="menu_advanced_unlock_settings">Desbloqueo avanzado</string> <string name="menu_advanced_unlock_settings">Desbloqueo avanzado</string>
<string name="biometric">Biometría</string> <string name="biometric">Biometría</string>
<string name="biometric_auto_open_prompt_title">Abrir petición de datos biométricos automáticamente</string> <string name="biometric_auto_open_prompt_title">Abrir petición automáticamente</string>
<string name="biometric_auto_open_prompt_summary">Abrir automáticamente la petición de datos biométricos cuando se define una clave biométrica para una base de datos</string> <string name="biometric_auto_open_prompt_summary">Solicitar automáticamente el desbloqueo avanzado si la base de datos está configurada para utilizarlo</string>
<string name="enable">Activar</string> <string name="enable">Activar</string>
<string name="disable">Desactivar</string> <string name="disable">Desactivar</string>
<string name="education_read_only_summary">Cambiar el modo de apertura de la sesión. <string name="education_read_only_summary">Cambiar el modo de apertura de la sesión.
\n \n
\n\"Protegido contra escritura\" evita cambios no deseados en la base de datos. \n\"Protegido contra escritura\" evita cambios no deseados en la base de datos.
\n\"Modificable\" le permite agregar, eliminar o modificar todos los elementos.</string> \n\"Modificable\" le permite agregar, eliminar o modificar todos los elementos como desee.</string>
<string name="lock_database_back_root_title">Presione hacia atrás en la raíz para bloquear</string> <string name="lock_database_back_root_title">Presione \'Atrás\' para bloquear</string>
<string name="content_description_repeat_toggle_password_visibility">Repetir la visibilidad de la contraseña</string> <string name="content_description_repeat_toggle_password_visibility">Repetir la visibilidad de la contraseña</string>
<string name="master_key">Llave maestra</string> <string name="master_key">Clave maestra</string>
<string name="security">Seguridad</string> <string name="security">Seguridad</string>
<string name="entry_history">Historial</string> <string name="entry_history">Historial</string>
<string name="entry_setup_otp">Configuración de contraseña de un solo uso</string> <string name="entry_setup_otp">Establecer una contraseña de un solo uso</string>
<string name="otp_type">Tipo de contraseña de un solo uso</string> <string name="otp_type">Tipo de contraseña de un solo uso</string>
<string name="otp_secret">Secreto</string> <string name="otp_secret">Secreto</string>
<string name="otp_period">Período (segundos)</string> <string name="otp_period">Período (segundos)</string>
@@ -378,16 +379,16 @@
<string name="error_otp_secret_key">Clave secreta debe estar en formato Base32.</string> <string name="error_otp_secret_key">Clave secreta debe estar en formato Base32.</string>
<string name="error_otp_counter">Contador debe estar entre %1$d y %2$d.</string> <string name="error_otp_counter">Contador debe estar entre %1$d y %2$d.</string>
<string name="error_save_database">No se puede guardar la base de datos.</string> <string name="error_save_database">No se puede guardar la base de datos.</string>
<string name="error_string_type">Este texto no encaja con la información requerida.</string> <string name="error_string_type">Este texto no coincide con el elemento requerido.</string>
<string name="error_create_database">No fue posible crear el archivo de base de datos.</string> <string name="error_create_database">No fue posible crear el archivo de base de datos.</string>
<string name="html_about_contribution">Parar lograr &lt;strong&gt;mantener nuestra libertad&lt;/strong&gt;, &lt;strong&gt;corregir errores&lt;/strong&gt;, &lt;strong&gt;agregar características&lt;/strong&gt; y &lt;strong&gt;siempre estar activos&lt;/strong&gt;, contamos con tu &lt;strong&gt;contribución&lt;/strong&gt;.</string> <string name="html_about_contribution">Parar lograr &lt;strong&gt;mantener nuestra libertad&lt;/strong&gt;, &lt;strong&gt;corregir errores&lt;/strong&gt;, &lt;strong&gt;agregar características&lt;/strong&gt; y &lt;strong&gt;estar siempre activos&lt;/strong&gt;, contamos con tu &lt;strong&gt;contribución&lt;/strong&gt;.</string>
<string name="content_description_add_item">Añadir elemento</string> <string name="content_description_add_item">Añadir elemento</string>
<string name="download_complete">Descarga completa!</string> <string name="download_complete">¡Completado!</string>
<string name="download_finalization">Finalizando…</string> <string name="download_finalization">Finalizando…</string>
<string name="download_progression">En progreso: %1$d%%</string> <string name="download_progression">En progreso: %1$d%%</string>
<string name="download_initialization">Inicializando…</string> <string name="download_initialization">Inicializando…</string>
<string name="download_attachment">Descargar %1$s</string> <string name="download_attachment">Descargar %1$s</string>
<string name="enable_auto_save_database_summary">Guardar la base de datos tras acciones importantes (en modo \"Modificable\")</string> <string name="enable_auto_save_database_summary">Guardar la base de datos después de cada acción importante (en modo \"Modificable\")</string>
<string name="enable_auto_save_database_title">Guardar base de datos automáticamente</string> <string name="enable_auto_save_database_title">Guardar base de datos automáticamente</string>
<string name="autofill_block">Bloquear autocompletado</string> <string name="autofill_block">Bloquear autocompletado</string>
<string name="keyboard_change">Cambiar teclado</string> <string name="keyboard_change">Cambiar teclado</string>
@@ -396,12 +397,12 @@
<string name="compression_none">Ninguna</string> <string name="compression_none">Ninguna</string>
<string name="compression">Compresión</string> <string name="compression">Compresión</string>
<string name="database_default_username_title">Nombre de usuario predeterminado</string> <string name="database_default_username_title">Nombre de usuario predeterminado</string>
<string name="settings_database_force_changing_master_key_next_time_summary">Requerir cambiar la contraseña maestra la próxima vez (una sola vez)</string> <string name="settings_database_force_changing_master_key_next_time_summary">Requerir cambiar la contraseña maestra la próxima vez (una vez)</string>
<string name="settings_database_force_changing_master_key_next_time_title">Forzar renovación la próxima vez</string> <string name="settings_database_force_changing_master_key_next_time_title">Forzar renovación la próxima vez</string>
<string name="settings_database_force_changing_master_key_summary">Requerir un cambio de contraseña maestra (días)</string> <string name="settings_database_force_changing_master_key_summary">Requerir un cambio de la contraseña maestra (días)</string>
<string name="settings_database_force_changing_master_key_title">Forzar renovación</string> <string name="settings_database_force_changing_master_key_title">Forzar renovación</string>
<string name="max_history_size_title">Tamaño máximo</string> <string name="max_history_size_title">Tamaño máximo</string>
<string name="advanced_unlock_explanation_summary">Usar desbloqueo avanzado para abrir una base de datos de manera más fácil</string> <string name="advanced_unlock_explanation_summary">Usar el desbloqueo avanzado para abrir una base de datos más fácilmente</string>
<string name="lock_database_show_button_summary">Muestra el botón de bloqueo en la interfaz</string> <string name="lock_database_show_button_summary">Muestra el botón de bloqueo en la interfaz</string>
<string name="lock_database_show_button_title">Mostrar botón de bloqueo</string> <string name="lock_database_show_button_title">Mostrar botón de bloqueo</string>
<string name="autofill_preference_title">Configuración de autocompletado</string> <string name="autofill_preference_title">Configuración de autocompletado</string>
@@ -410,8 +411,8 @@
<string name="warning_database_read_only">Otorga acceso de escritura para guardar cambios en la base de datos</string> <string name="warning_database_read_only">Otorga acceso de escritura para guardar cambios en la base de datos</string>
<string name="show_recent_files_summary">Mostrar ubicaciones de bases de datos recientes</string> <string name="show_recent_files_summary">Mostrar ubicaciones de bases de datos recientes</string>
<string name="show_recent_files_title">Mostrar archivos recientes</string> <string name="show_recent_files_title">Mostrar archivos recientes</string>
<string name="remember_database_locations_summary">Recordar la ubicación de las bases de datos</string> <string name="remember_database_locations_summary">Lleva un registro de los lugares donde se almacenan las bases de datos</string>
<string name="remember_database_locations_title">Guardar ubicaciones de bases de datos</string> <string name="remember_database_locations_title">Recordar las ubicaciones de las bases de datos</string>
<string name="contains_duplicate_uuid">La base de datos contiene UUIDs duplicados.</string> <string name="contains_duplicate_uuid">La base de datos contiene UUIDs duplicados.</string>
<string name="menu_restore_entry_history">Restaurar historial</string> <string name="menu_restore_entry_history">Restaurar historial</string>
<string name="menu_empty_recycle_bin">Vaciar papelera de reciclaje</string> <string name="menu_empty_recycle_bin">Vaciar papelera de reciclaje</string>
@@ -420,12 +421,12 @@
<string name="invalid_db_same_uuid">%1$s con la misma UUID %2$s ya existe.</string> <string name="invalid_db_same_uuid">%1$s con la misma UUID %2$s ya existe.</string>
<string name="error_label_exists">Esta etiqueta ya existe.</string> <string name="error_label_exists">Esta etiqueta ya existe.</string>
<string name="discard">Descartar</string> <string name="discard">Descartar</string>
<string name="discard_changes">¿Descartar cambios\?</string> <string name="discard_changes">¿Descartar los cambios\?</string>
<string name="validate">Validar</string> <string name="validate">Validar</string>
<string name="contribution">Contribución</string> <string name="contribution">Contribución</string>
<string name="contact">Contactar</string> <string name="contact">Contacto</string>
<string name="error_otp_period">Período debe estar entre %1$d y %2$d segundos.</string> <string name="error_otp_period">El período debe estar entre %1$d y %2$d segundos.</string>
<string name="error_copy_group_here">No se puede copiar un grupo aquí.</string> <string name="error_copy_group_here">No puede copiar un grupo aquí.</string>
<string name="database_data_compression_summary">La compresión de datos reduce el tamaño de la base de datos</string> <string name="database_data_compression_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="database_data_compression_title">Compresión de datos</string>
<string name="warning_empty_keyfile">No se recomienda agregar un archivo de claves vacío.</string> <string name="warning_empty_keyfile">No se recomienda agregar un archivo de claves vacío.</string>
@@ -434,19 +435,19 @@
<string name="warning_replace_file">Al cargar este archivo reemplazará el existente.</string> <string name="warning_replace_file">Al cargar este archivo reemplazará el existente.</string>
<string name="warning_permanently_delete_nodes">¿Borrar los nodos seleccionados de forma permanente\?</string> <string name="warning_permanently_delete_nodes">¿Borrar los nodos seleccionados de forma permanente\?</string>
<string name="warning_database_link_revoked">El acceso al archivo fue revocado por el administrador de archivos</string> <string name="warning_database_link_revoked">El acceso al archivo fue revocado por el administrador de archivos</string>
<string name="command_execution">Ejecutando el comando </string> <string name="command_execution">Ejecutando el comando…</string>
<string name="hide_broken_locations_summary">Ocultar enlaces rotos en la lista de bases de datos recientes</string> <string name="hide_broken_locations_summary">Ocultar enlaces rotos en la lista de bases de datos recientes</string>
<string name="hide_broken_locations_title">Ocultar enlaces de bases de datos rotos</string> <string name="hide_broken_locations_title">Ocultar enlaces de bases de datos rotos</string>
<string name="remember_keyfile_locations_summary">Realiza un seguimiento de dónde se almacenan los archivos de claves</string> <string name="remember_keyfile_locations_summary">Realiza un seguimiento de dónde se almacenan los archivos de claves</string>
<string name="remember_keyfile_locations_title">Recuerdar las ubicaciones de los archivos de claves</string> <string name="remember_keyfile_locations_title">Recordar las ubicaciones de los archivos de clave</string>
<string name="subdomain_search_summary">Buscar dominios web con restricciones de subdominios</string> <string name="subdomain_search_summary">Buscar dominios web con restricciones de subdominios</string>
<string name="subdomain_search_title">Búsqueda de subdominio</string> <string name="subdomain_search_title">Búsqueda de subdominio</string>
<string name="auto_focus_search_summary">Solicite una búsqueda al abrir una base de datos</string> <string name="auto_focus_search_summary">Solicite una búsqueda al abrir una base de datos</string>
<string name="auto_focus_search_title">Búsqueda rápida</string> <string name="auto_focus_search_title">Búsqueda rápida</string>
<string name="menu_delete_entry_history">Borrar historial</string> <string name="menu_delete_entry_history">Eliminar el historial</string>
<string name="error_otp_digits">El token debe tener de %1$d a %2$d dígitos.</string> <string name="error_otp_digits">El token debe contener de %1$d a %2$d dígitos.</string>
<string name="entry_attachments">Archivos adjuntos</string> <string name="entry_attachments">Archivos adjuntos</string>
<string name="entry_add_attachment">Adjuntar</string> <string name="entry_add_attachment">Añadir el archivo adjunto</string>
<string name="content_description_credentials_information">Información de credenciales</string> <string name="content_description_credentials_information">Información de credenciales</string>
<string name="database_opened">Base de datos abierta</string> <string name="database_opened">Base de datos abierta</string>
<string name="education_add_attachment_title">Adjuntar</string> <string name="education_add_attachment_title">Adjuntar</string>
@@ -456,4 +457,94 @@
<string name="warning_file_too_big">Se supone que una base de datos KeePass contiene solo pequeños archivos de utilidad (como archivos de clave PGP). <string name="warning_file_too_big">Se supone que una base de datos KeePass contiene solo pequeños archivos de utilidad (como archivos de clave PGP).
\n \n
\nLa base de datos puede volverse muy grande y reducir su rendimiento con esta subida.</string> \nLa base de datos puede volverse muy grande y reducir su rendimiento con esta subida.</string>
<string name="recycle_bin_group_title">Grupo de la papelera de reciclaje</string>
<string name="filter">Filtrar</string>
<string name="database_data_remove_unlinked_attachments_summary">Elimina los archivos adjuntos contenidos en la base de datos pero no vinculados a una entrada</string>
<string name="database_data_remove_unlinked_attachments_title">Eliminar los datos no vinculados</string>
<string name="data">Datos</string>
<string name="advanced_unlock_delete_all_key_warning">¿Borrar todas las claves de encriptación relacionadas con el reconocimiento de desbloqueo avanzado\?</string>
<string name="advanced_unlock_timeout">Tiempo límite de desbloqueo avanzado</string>
<string name="temp_advanced_unlock_timeout_summary">Duración del uso de desbloqueo avanzado antes de borrar su contenido</string>
<string name="temp_advanced_unlock_timeout_title">Expiración de desbloqueo avanzado</string>
<string name="temp_advanced_unlock_enable_summary">No almacenar ningún contenido encriptado para utilizar el desbloqueo avanzado</string>
<string name="temp_advanced_unlock_enable_title">Desbloqueo avanzado temporal</string>
<string name="device_credential_unlock_enable_summary">Le permite usar la credenciales de su dispositivo para abrir la base de datos</string>
<string name="device_credential_unlock_enable_title">Desbloqueo de las credenciales del dispositivo</string>
<string name="advanced_unlock_tap_delete">Toque para eliminar las teclas de desbloqueo avanzadas</string>
<string name="content">Contenido</string>
<string name="clipboard_explanation_summary">Copiar los campos de entrada usando el portapapeles de su dispositivo</string>
<string name="device_credential">Credenciales del dispositivo</string>
<string name="credential_before_click_advanced_unlock_button">Introduzca la contraseña y luego haga clic en el botón \"Desbloqueo avanzado\".</string>
<string name="advanced_unlock_prompt_not_initialized">No se pudo inicializar el indicador de desbloqueo avanzado.</string>
<string name="advanced_unlock_scanning_error">Error de desbloqueo avanzado: %1$s</string>
<string name="advanced_unlock_not_recognized">No se pudo reconocer la impresión de desbloqueo avanzado</string>
<string name="advanced_unlock_invalid_key">No se pudo leer la llave de desbloqueo avanzada. Por favor, bórrela y repita el procedimiento de reconocimiento de desbloqueo.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extraer la credencial de la base de datos con datos de desbloqueo avanzado</string>
<string name="advanced_unlock_prompt_extract_credential_title">Abrir la base de datos con reconocimiento de desbloqueo avanzado</string>
<string name="advanced_unlock_prompt_store_credential_message">Advertencia: Aún debes recordar tu contraseña maestra si usas el reconocimiento de desbloqueo avanzado.</string>
<string name="advanced_unlock_prompt_store_credential_title">Reconocimiento de desbloqueo avanzado</string>
<string name="open_advanced_unlock_prompt_store_credential">Abrir el indicador de desbloqueo avanzado para almacenar las credenciales</string>
<string name="open_advanced_unlock_prompt_unlock_database">Abrir el aviso de desbloqueo avanzado para desbloquear la base de datos</string>
<string name="keystore_not_accessible">El almacén de claves no está debidamente inicializado.</string>
<string name="biometric_security_update_required">Se requiere una actualización de la seguridad biométrica.</string>
<string name="configure_biometric">No se ha inscrito ninguna credencial biométrica o del dispositivo.</string>
<string name="warning_empty_keyfile_explanation">El contenido del archivo clave nunca debe modificarse y, en el mejor de los casos, debe contener datos generados al azar.</string>
<string name="warning_empty_recycle_bin">¿Borrar permanentemente todos los nodos de la papelera de reciclaje\?</string>
<string name="registration_mode">Modo de registro</string>
<string name="save_mode">Modo de guardado</string>
<string name="search_mode">Modo de búsqueda</string>
<string name="contains_duplicate_uuid_procedure">¿Resolver el problema generando nuevos UUID para que los duplicados continúen\?</string>
<string name="menu_keystore_remove_key">Borrar la clave de desbloqueo avanzado</string>
<string name="error_field_name_already_exists">El nombre del campo ya existe.</string>
<string name="error_registration_read_only">Guardar un nuevo elemento no está permitido en una base de datos de sólo lectura</string>
<string name="settings_database_recommend_changing_master_key_title">Recomendar la renovación</string>
<string name="max_history_size_summary">Limitar el tamaño del historial (en bytes) por entrada</string>
<string name="max_history_items_summary">Limitar el número de elementos del historial por entrada</string>
<string name="max_history_items_title">Número máximo</string>
<string name="device_keyboard_setting_title">Configuración del teclado del dispositivo</string>
<string name="database_custom_color_title">Color personalizado de la base de datos</string>
<string name="settings_database_recommend_changing_master_key_summary">Recomendar cambiar la contraseña maestra (días)</string>
<string name="notification">Notificación</string>
<string name="hide_expired_entries_title">Ocultar las entradas expiradas</string>
<string name="keyboard_search_share_title">Buscar información compartida</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="upload_attachment">Subir %1$s</string>
<string name="education_setup_OTP_summary">Configurar la gestión de contraseñas de una sola vez (HOTP / TOTP) para generar un token solicitado para la autenticación de dos factores (2FA).</string>
<string name="education_setup_OTP_title">Establecer la contaseña de un solo uso</string>
<string name="education_advanced_unlock_summary">Vincule su contraseña con su credencial biométrica o del dispositivo escaneada para desbloquear rápidamente su base de datos.</string>
<string name="education_advanced_unlock_title">Desbloqueo avanzado de la base de datos</string>
<string name="autofill_read_only_save">No se permite guardar datos en una base de datos abierta como de sólo lectura.</string>
<string name="autofill_block_restart">Reiniciar la aplicación que contiene el formulario para activar el bloqueo.</string>
<string name="autofill_web_domain_blocklist_summary">Lista de bloqueo que impide el llenado automático de los dominios web</string>
<string name="autofill_web_domain_blocklist_title">Lista de bloqueo de los dominios web</string>
<string name="autofill_application_id_blocklist_summary">Lista de bloqueo que impide el llenado automático de las aplicaciones</string>
<string name="autofill_application_id_blocklist_title">Lista de bloqueo de las aplicaciones</string>
<string name="autofill_ask_to_save_data_summary">Pedir que se guarden los datos cuando se valide un formulario</string>
<string name="autofill_ask_to_save_data_title">Pedir que se guarden los datos</string>
<string name="autofill_save_search_info_summary">Intente guardar la información de la búsqueda cuando haga una selección de entrada manual</string>
<string name="autofill_save_search_info_title">Guardar la información de la búsqueda</string>
<string name="autofill_auto_search_summary">Sugerir automáticamente los resultados de la búsqueda desde el dominio web o el ID de la aplicación</string>
<string name="autofill_auto_search_title">Búsqueda automática</string>
<string name="autofill_close_database_summary">Cerrar la base de datos después de una selección de autocompletado</string>
<string name="autofill_close_database_title">Cerrar la base de datos</string>
<string name="enter">Entrar</string>
<string name="backspace">Retroceder</string>
<string name="select_entry">Seleccionar la entrada</string>
<string name="back_to_previous_keyboard">Volver al teclado anterior</string>
<string name="custom_fields">Campos personalizados</string>
<string name="keyboard_previous_lock_summary">Cambiar automáticamente al teclado anterior después de bloquear la base de datos</string>
<string name="keyboard_previous_lock_title">Bloquear la base de datos</string>
<string name="keyboard_previous_fill_in_summary">Cambiar automáticamente al teclado anterior después de ejecutar \"Acción de la tecla automática\"</string>
<string name="keyboard_previous_fill_in_title">Acción de la tecla automática</string>
<string name="keyboard_previous_database_credentials_summary">Cambiar automáticamente al teclado anterior en la pantalla de credenciales de la base de datos</string>
<string name="keyboard_previous_database_credentials_title">Pantalla de credenciales de la base de datos</string>
<string name="keyboard_auto_go_action_title">Acción de la tecla automática</string>
<string name="keyboard_save_search_info_summary">Intentar guardar la información compartida cuando hagas una selección de entrada manual</string>
<string name="keyboard_save_search_info_title">Guardar información compartida</string>
<string name="keyboard_search_share_summary">Buscar automáticamente la información compartida para llenar el teclado</string>
<string name="show_uuid_summary">Muestra el UUID vinculado a una entrada</string>
<string name="show_uuid_title">Mostrar UUID</string>
<string name="error_rebuild_list">No es posible reconstruir adecuadamente la lista.</string>
<string name="error_database_uri_null">La URI de la base de datos no puede ser recuperada.</string>
</resources> </resources>

View File

@@ -262,7 +262,6 @@
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string-array name="timeout_options"> <string-array name="timeout_options">
<item>5 secondes</item> <item>5 secondes</item>
<item>10 secondes</item> <item>10 secondes</item>
@@ -514,7 +513,6 @@
<string name="keyboard_save_search_info_summary">Essayer denregistrer les informations partagées lors de la sélection manuelle dune entrée</string> <string name="keyboard_save_search_info_summary">Essayer denregistrer les informations partagées lors de la sélection manuelle dune entrée</string>
<string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string> <string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string>
<string name="notification">Notification</string> <string name="notification">Notification</string>
<string name="crypto_object_not_initialized">Impossible de récupérer l\'objet crypto.</string>
<string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string> <string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string>
<string name="warning_empty_recycle_bin">Supprimer définitivement tous les nœuds de la corbeille \?</string> <string name="warning_empty_recycle_bin">Supprimer définitivement tous les nœuds de la corbeille \?</string>
<string name="registration_mode">Mode enregistrement</string> <string name="registration_mode">Mode enregistrement</string>
@@ -552,4 +550,15 @@
<string name="temp_advanced_unlock_enable_title">Déverrouillage avancé temporaire</string> <string name="temp_advanced_unlock_enable_title">Déverrouillage avancé temporaire</string>
<string name="advanced_unlock_tap_delete">Appuyez pour supprimer les clés de déverrouillage avancées</string> <string name="advanced_unlock_tap_delete">Appuyez pour supprimer les clés de déverrouillage avancées</string>
<string name="content">Contenu</string> <string name="content">Contenu</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_rebuild_list">Impossible de reconstruire correctement la liste.</string>
<string name="error_database_uri_null">L\'URI de la base de données ne peut pas être récupéré.</string>
<string name="autofill_inline_suggestions_keyboard">Suggestions de remplissage automatique ajoutées.</string>
<string name="autofill_inline_suggestions_summary">Tente d\'afficher des suggestions de remplissage automatique directement à partir d\'un clavier compatible</string>
<string name="autofill_inline_suggestions_title">Suggestions en ligne</string>
<string name="warning_database_info_changed_options">Écraser les modifications externes en sauvegardant la base de données ou recharger-la avec les dernières modifications.</string>
<string name="warning_database_revoked">Accès au dossier révoqué par le gestionnaire de fichiers, fermer la base de données et la rouvrir à partir de son emplacement.</string>
<string name="warning_database_info_changed">Les informations contenues dans votre fichier de base de données ont été modifiées en dehors de l\'application.</string>
<string name="menu_reload_database">Recharger la base de données</string>
</resources> </resources>

View File

@@ -394,7 +394,6 @@
<string name="html_text_dev_feature_contibute">&lt;strong&gt;Doprinosom&lt;/strong&gt;,</string> <string name="html_text_dev_feature_contibute">&lt;strong&gt;Doprinosom&lt;/strong&gt;,</string>
<string name="education_entry_new_field_title">Dodaj prilagođena polja</string> <string name="education_entry_new_field_title">Dodaj prilagođena polja</string>
<string name="education_lock_summary">Zaključaj bazu podataka brzo, aplikaciju možeš postaviti tako da bazu nakon nekog vremena zaključa i kad se ekran isključi.</string> <string name="education_lock_summary">Zaključaj bazu podataka brzo, aplikaciju možeš postaviti tako da bazu nakon nekog vremena zaključa i kad se ekran isključi.</string>
<string name="kdf_Argon2">Argon2</string>
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="show_recent_files_summary">Prikaži mjesto nedavnih baza podataka</string> <string name="show_recent_files_summary">Prikaži mjesto nedavnih baza podataka</string>
<string name="education_advanced_unlock_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.</string> <string name="education_advanced_unlock_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.</string>
@@ -491,7 +490,6 @@
<string name="keyboard_save_search_info_summary">Pokušaj spremiti dijeljene informacije prilikom odabira ručnog unosa</string> <string name="keyboard_save_search_info_summary">Pokušaj spremiti dijeljene informacije prilikom odabira ručnog unosa</string>
<string name="keyboard_save_search_info_title">Spremi dijeljene informacije</string> <string name="keyboard_save_search_info_title">Spremi dijeljene informacije</string>
<string name="warning_empty_recycle_bin">Trajno izbrisati sve čvorove iz smeća\?</string> <string name="warning_empty_recycle_bin">Trajno izbrisati sve čvorove iz smeća\?</string>
<string name="crypto_object_not_initialized">Nije moguće dohvatiti kripto objekt.</string>
<string name="biometric_security_update_required">Potrebno je aktualizirati biometrijsku zaštitu.</string> <string name="biometric_security_update_required">Potrebno je aktualizirati biometrijsku zaštitu.</string>
<string name="configure_biometric">Ne postoji biometrijski ključ niti podaci za prijavu uređaja.</string> <string name="configure_biometric">Ne postoji biometrijski ključ niti podaci za prijavu uređaja.</string>
<string name="registration_mode">Modus registracije</string> <string name="registration_mode">Modus registracije</string>
@@ -527,4 +525,8 @@
<string name="temp_advanced_unlock_enable_summary">Nemoj spremati šifrirani sadržaj za napredno otključavanje</string> <string name="temp_advanced_unlock_enable_summary">Nemoj spremati šifrirani sadržaj za napredno otključavanje</string>
<string name="content">Sadržaj</string> <string name="content">Sadržaj</string>
<string name="temp_advanced_unlock_enable_title">Privremeno napredno otključavanje</string> <string name="temp_advanced_unlock_enable_title">Privremeno napredno otključavanje</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_rebuild_list">Nije moguće ispravno obnoviti popis.</string>
<string name="error_database_uri_null">URI baze podataka nije moguće dobiti.</string>
</resources> </resources>

View File

@@ -317,7 +317,6 @@
<string name="contribute">Támogatás</string> <string name="contribute">Támogatás</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Alkalmazástéma</string> <string name="style_choose_title">Alkalmazástéma</string>
<string name="style_choose_summary">Az alkalmazásban használt téma</string> <string name="style_choose_summary">Az alkalmazásban használt téma</string>
<string name="icon_pack_choose_title">Ikoncsomag</string> <string name="icon_pack_choose_title">Ikoncsomag</string>
@@ -465,7 +464,6 @@
<string name="database_data_remove_unlinked_attachments_summary">Eltávolítja azokat a mellékleteket, melyek az adatbázisban szerepelnek, de nem tartoznak bejegyzéshez</string> <string name="database_data_remove_unlinked_attachments_summary">Eltávolítja azokat a mellékleteket, melyek az adatbázisban szerepelnek, de nem tartoznak bejegyzéshez</string>
<string name="database_data_remove_unlinked_attachments_title">Nem összekapcsolt adatok eltávolítása</string> <string name="database_data_remove_unlinked_attachments_title">Nem összekapcsolt adatok eltávolítása</string>
<string name="data">Adatok</string> <string name="data">Adatok</string>
<string name="crypto_object_not_initialized">A titkosítási objektum nem kérhető le.</string>
<string name="biometric_security_update_required">Biometrikus biztonsági frissítés szükséges.</string> <string name="biometric_security_update_required">Biometrikus biztonsági frissítés szükséges.</string>
<string name="configure_biometric">Nincs biometrikus vagy eszközazonosító beállítva.</string> <string name="configure_biometric">Nincs biometrikus vagy eszközazonosító beállítva.</string>
<string name="warning_empty_keyfile_explanation">A kulcsfájl tartalmának sosem szabad megváltoznia, és a legjobb esetben véletlenszerűen előállított adatokat kellene tartalmaznia.</string> <string name="warning_empty_keyfile_explanation">A kulcsfájl tartalmának sosem szabad megváltoznia, és a legjobb esetben véletlenszerűen előállított adatokat kellene tartalmaznia.</string>

View File

@@ -27,11 +27,11 @@
<string name="add_group">Aggiungi gruppo</string> <string name="add_group">Aggiungi gruppo</string>
<string name="encryption_algorithm">Algoritmo di cifratura</string> <string name="encryption_algorithm">Algoritmo di cifratura</string>
<string name="app_timeout">Scadenza app</string> <string name="app_timeout">Scadenza app</string>
<string name="app_timeout_summary">Tempo di inattività prima del blocco della base di dati</string> <string name="app_timeout_summary">Tempo di inattività prima del blocco del database</string>
<string name="application">App</string> <string name="application">App</string>
<string name="menu_app_settings">Impostazioni app</string> <string name="menu_app_settings">Impostazioni app</string>
<string name="brackets">Parentesi</string> <string name="brackets">Parentesi</string>
<string name="file_manager_install_description">Un file manager che accetta l\'azione Intent. ACTION_CREATE_DOCUMENT e ACTION_OPEN_DOCUMENT sono necessari per creare, aprire e salvare i file del database.</string> <string name="file_manager_install_description">Un file manager che accetta intent action ACTION_CREATE_DOCUMENT e ACTION_OPEN_DOCUMENT è necessario creare, aprire e salvare i file del database.</string>
<string name="clipboard_cleared">Appunti eliminati</string> <string name="clipboard_cleared">Appunti eliminati</string>
<string name="clipboard_error_title">Errore negli appunti</string> <string name="clipboard_error_title">Errore negli appunti</string>
<string name="clipboard_error">Alcuni dispositivi non permettono alle app di usare gli appunti.</string> <string name="clipboard_error">Alcuni dispositivi non permettono alle app di usare gli appunti.</string>
@@ -39,15 +39,15 @@
<string name="clipboard_timeout">Scadenza appunti</string> <string name="clipboard_timeout">Scadenza appunti</string>
<string name="clipboard_timeout_summary">Tempo prima di eliminare gli appunti (se supportato dal dispositivo)</string> <string name="clipboard_timeout_summary">Tempo prima di eliminare gli appunti (se supportato dal dispositivo)</string>
<string name="select_to_copy">Copia %1$s negli appunti</string> <string name="select_to_copy">Copia %1$s negli appunti</string>
<string name="retrieving_db_key">Creazione file chiave base di dati</string> <string name="retrieving_db_key">Recupero chiave database</string>
<string name="database">Banca dati</string> <string name="database">Database</string>
<string name="decrypting_db">Decodifica contenuto base di dati</string> <string name="decrypting_db">Decodifica contenuto database…</string>
<string name="default_checkbox">Usa come base di dati predefinita</string> <string name="default_checkbox">Usa come database predefinito</string>
<string name="digits">Numeri</string> <string name="digits">Numeri</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft è un programma &lt;strong&gt;open-source&lt;/strong&gt; e &lt;strong&gt;senza pubblicità&lt;/strong&gt;. <string name="html_about_licence">KeePassDX © %1$d Kunzisoft è un programma &lt;strong&gt;open-source&lt;/strong&gt; e &lt;strong&gt;senza pubblicità&lt;/strong&gt;.
\nViene distribuito sotto le condizioni della licenza &lt;strong&gt;GPL versione 3&lt;/strong&gt; o successiva, senza alcuna garanzia.</string> \nViene distribuito sotto le condizioni della licenza &lt;strong&gt;GPL versione 3&lt;/strong&gt; o successiva, senza alcuna garanzia.</string>
<string name="entry_notes">Note</string> <string name="entry_notes">Note</string>
<string name="select_database_file">Apri una base di dati esistente</string> <string name="select_database_file">Apri un database esistente</string>
<string name="entry_accessed">Ultimo accesso</string> <string name="entry_accessed">Ultimo accesso</string>
<string name="entry_cancel">Annulla</string> <string name="entry_cancel">Annulla</string>
<string name="entry_confpassword">Conferma password</string> <string name="entry_confpassword">Conferma password</string>
@@ -64,15 +64,15 @@
<string name="error_arc4">La codifica a flusso Arcfour non è supportata.</string> <string name="error_arc4">La codifica a flusso Arcfour non è supportata.</string>
<string name="error_can_not_handle_uri">KeePassDX non può gestire questo URI.</string> <string name="error_can_not_handle_uri">KeePassDX non può gestire questo URI.</string>
<string name="error_file_not_create">Impossibile creare il file</string> <string name="error_file_not_create">Impossibile creare il file</string>
<string name="error_invalid_db">Impossibile leggere la base di dati.</string> <string name="error_invalid_db">Impossibile leggere il database.</string>
<string name="error_invalid_path">Assicurati che il percorso sia corretto.</string> <string name="error_invalid_path">Assicurati che il percorso sia corretto.</string>
<string name="error_no_name">Inserisci un nome.</string> <string name="error_no_name">Inserisci un nome.</string>
<string name="error_out_of_memory">Memoria insufficiente per caricare l\'intera base di dati.</string> <string name="error_out_of_memory">Memoria insufficiente per caricare l\'intero database.</string>
<string name="error_pass_gen_type">Deve essere selezionato almeno un tipo di generazione password.</string> <string name="error_pass_gen_type">Deve essere selezionato almeno un tipo di generazione password.</string>
<string name="error_pass_match">Le password non corrispondono.</string> <string name="error_pass_match">Le password non corrispondono.</string>
<string name="error_rounds_too_large">«Livello» troppo alto. Impostato a 2147483648.</string> <string name="error_rounds_too_large">«Livello» troppo alto. Impostato a 2147483648.</string>
<string name="error_string_key">Ogni stringa deve avere un nome.</string> <string name="error_string_key">Ogni stringa deve avere un nome.</string>
<string name="error_wrong_length">Inserisci un numero naturale positivo nel campo «lunghezza».</string> <string name="error_wrong_length">Inserisci un numero intero positivo nel campo \"Lunghezza\".</string>
<string name="field_name">Nome campo</string> <string name="field_name">Nome campo</string>
<string name="field_value">Valore campo</string> <string name="field_value">Valore campo</string>
<string name="file_not_found_content">File non trovato. Prova a riaprirlo dal tuo gestore di file.</string> <string name="file_not_found_content">File non trovato. Prova a riaprirlo dal tuo gestore di file.</string>
@@ -87,24 +87,24 @@
<string name="hint_pass">Password</string> <string name="hint_pass">Password</string>
<string name="invalid_credentials">Non è possibile leggere le credenziali.</string> <string name="invalid_credentials">Non è possibile leggere le credenziali.</string>
<string name="invalid_algorithm">Algoritmo errato.</string> <string name="invalid_algorithm">Algoritmo errato.</string>
<string name="invalid_db_sig">Formato della base di dati non riconosciuto.</string> <string name="invalid_db_sig">Formato del database non riconosciuto.</string>
<string name="keyfile_is_empty">Il file chiave è vuoto.</string> <string name="keyfile_is_empty">Il file chiave è vuoto.</string>
<string name="length">Lunghezza</string> <string name="length">Lunghezza</string>
<string name="list_size_title">Dimensione elenco elementi</string> <string name="list_size_title">Dimensione elenco elementi</string>
<string name="list_size_summary">Dimensione del testo nell\'elenco del gruppo</string> <string name="list_size_summary">Dimensione del testo nell\'elenco del gruppo</string>
<string name="loading_database">Caricamento della base di dati</string> <string name="loading_database">Caricamento del database</string>
<string name="lowercase">Minuscole</string> <string name="lowercase">Minuscole</string>
<string name="hide_password_title">Nascondi le password</string> <string name="hide_password_title">Nascondi le password</string>
<string name="hide_password_summary">Maschera le password (***) in modo predefinito</string> <string name="hide_password_summary">Maschera le password (***) in modo predefinito</string>
<string name="about">Informazioni</string> <string name="about">Informazioni</string>
<string name="menu_change_key_settings">Modifica chiave principale</string> <string name="menu_change_key_settings">Modifica chiave principale</string>
<string name="settings">Impostazioni</string> <string name="settings">Impostazioni</string>
<string name="menu_database_settings">Impostazioni base di dati</string> <string name="menu_database_settings">Impostazioni database</string>
<string name="menu_delete">Elimina</string> <string name="menu_delete">Elimina</string>
<string name="menu_donate">Dona</string> <string name="menu_donate">Dona</string>
<string name="menu_edit">Modifica</string> <string name="menu_edit">Modifica</string>
<string name="menu_hide_password">Nascondi password</string> <string name="menu_hide_password">Nascondi password</string>
<string name="menu_lock">Blocca la base di dati</string> <string name="menu_lock">Blocca database</string>
<string name="menu_open">Apri</string> <string name="menu_open">Apri</string>
<string name="menu_search">Cerca</string> <string name="menu_search">Cerca</string>
<string name="menu_showpass">Mostra password</string> <string name="menu_showpass">Mostra password</string>
@@ -115,16 +115,16 @@
<string name="no_url_handler">Installa un browser web per aprire questo URL.</string> <string name="no_url_handler">Installa un browser web per aprire questo URL.</string>
<string name="omit_backup_search_title">Non cercare nelle voci di backup</string> <string name="omit_backup_search_title">Non cercare nelle voci di backup</string>
<string name="omit_backup_search_summary">Ometti i gruppi «Backup» e «Cestino» dai risultati di ricerca</string> <string name="omit_backup_search_summary">Ometti i gruppi «Backup» e «Cestino» dai risultati di ricerca</string>
<string name="progress_create">Creazione di una nuova base di dati</string> <string name="progress_create">Creazione di un nuovo database</string>
<string name="progress_title">In corso…</string> <string name="progress_title">In corso…</string>
<string name="protection">Protezione</string> <string name="protection">Protezione</string>
<string name="read_only">Sola lettura</string> <string name="read_only">Sola lettura</string>
<string name="read_only_warning">KeePassDX richiede l\'autorizzazione di scrittura per poter modificare la tua base di dati.</string> <string name="read_only_warning">A seconda del tuo file manager, KeepassDX potrebbe non riuscire a scrivere sulla memoria.</string>
<string name="content_description_remove_from_list">Elimina</string> <string name="content_description_remove_from_list">Elimina</string>
<string name="root">Root</string> <string name="root">Root</string>
<string name="rounds">Livello cifratura</string> <string name="rounds">Livello cifratura</string>
<string name="rounds_explanation">Livelli di cifratura aggiuntivi forniscono una maggiore protezione contro attacchi di tipo forza bruta, ma può rallentare il caricamento e il salvataggio.</string> <string name="rounds_explanation">Livelli di cifratura aggiuntivi forniscono una maggiore protezione contro attacchi di tipo forza bruta, ma può rallentare il caricamento e il salvataggio.</string>
<string name="saving_database">Salvataggio della base di dati</string> <string name="saving_database">Salvataggio del database</string>
<string name="space">Spazio</string> <string name="space">Spazio</string>
<string name="search_label">Cerca</string> <string name="search_label">Cerca</string>
<string name="sort_db">Ordine naturale</string> <string name="sort_db">Ordine naturale</string>
@@ -132,16 +132,16 @@
<string name="search">Cerca</string> <string name="search">Cerca</string>
<string name="search_results">Risultati della ricerca</string> <string name="search_results">Risultati della ricerca</string>
<string name="underline">Trattino basso</string> <string name="underline">Trattino basso</string>
<string name="unsupported_db_version">Versione della base di dati non supportata.</string> <string name="unsupported_db_version">Versione del database non supportata.</string>
<string name="uppercase">Maiuscole</string> <string name="uppercase">Maiuscole</string>
<string name="warning">Attenzione</string> <string name="warning">Attenzione</string>
<string name="warning_password_encoding">Evita password con caratteri al di fuori del formato di codifica del testo nel file di base di dati (i caratteri non riconosciuti vengono convertiti nella stessa lettera).</string> <string name="warning_password_encoding">Evita password con caratteri al di fuori del formato di codifica del testo nel file del database (i caratteri non riconosciuti vengono convertiti nella stessa lettera).</string>
<string name="version_label">Versione %1$s</string> <string name="version_label">Versione %1$s</string>
<string name="encrypted_value_stored">Password criptata salvata</string> <string name="encrypted_value_stored">Password criptata salvata</string>
<string name="no_credentials_stored">Questa base di dati non contiene alcuna credenziale.</string> <string name="no_credentials_stored">Questo database non contiene alcuna credenziale.</string>
<string name="education_unlock_summary">Inserisci la password o il file chiave per sbloccare la base di dati. <string name="education_unlock_summary">Inserisci la password o il file chiave per sbloccare la base di dati.
\n \n
\nEseguire il backup del file di base di dati in un luogo sicuro dopo ogni modifica.</string> \nEseguire il backup del file del database in un luogo sicuro dopo ogni modifica.</string>
<string-array name="timeout_options"> <string-array name="timeout_options">
<item>5 secondi</item> <item>5 secondi</item>
<item>10 secondi</item> <item>10 secondi</item>
@@ -173,7 +173,7 @@
<string name="menu_cancel">Annulla</string> <string name="menu_cancel">Annulla</string>
<string name="menu_file_selection_read_only">Sola lettura</string> <string name="menu_file_selection_read_only">Sola lettura</string>
<string name="menu_open_file_read_and_write">Modificabile</string> <string name="menu_open_file_read_and_write">Modificabile</string>
<string name="encryption_explanation">Algoritmo di cifratura della base di dati usato per tutti i dati.</string> <string name="encryption_explanation">Algoritmo di cifratura del database usato per tutti i dati.</string>
<string name="kdf_explanation">Per generare la chiave per l\'algoritmo di cifratura, la chiave principale viene trasformata usando una funzione di derivazione della chiave (con un sale casuale).</string> <string name="kdf_explanation">Per generare la chiave per l\'algoritmo di cifratura, la chiave principale viene trasformata usando una funzione di derivazione della chiave (con un sale casuale).</string>
<string name="memory_usage">Utilizzo di memoria</string> <string name="memory_usage">Utilizzo di memoria</string>
<string name="memory_usage_explanation">Quantità di memoria (in byte) utilizzabili dalla funzione di derivazione della chiave.</string> <string name="memory_usage_explanation">Quantità di memoria (in byte) utilizzabili dalla funzione di derivazione della chiave.</string>
@@ -197,7 +197,7 @@
<string name="autofill_service_name">Autocompletamento di KeePassDX</string> <string name="autofill_service_name">Autocompletamento di KeePassDX</string>
<string name="autofill_sign_in_prompt">Accedi con KeePassDX</string> <string name="autofill_sign_in_prompt">Accedi con KeePassDX</string>
<string name="set_autofill_service_title">Imposta servizio predefinito di autocompletamento</string> <string name="set_autofill_service_title">Imposta servizio predefinito di autocompletamento</string>
<string name="autofill_explanation_summary">Attiva l\'autocompletamento per compilare velocemente i moduli in altre app</string> <string name="autofill_explanation_summary">Attiva l\'autocompletamento per riempire velocemente i campi in altre app</string>
<string name="password_size_title">Dimensione password generata</string> <string name="password_size_title">Dimensione password generata</string>
<string name="password_size_summary">Imposta la dimensione predefinita delle password generate</string> <string name="password_size_summary">Imposta la dimensione predefinita delle password generate</string>
<string name="list_password_generator_options_title">Caratteri password</string> <string name="list_password_generator_options_title">Caratteri password</string>
@@ -207,19 +207,19 @@
<string name="clipboard_warning">Se l\'eliminazione automatica degli appunti fallisce, cancellali manualmente.</string> <string name="clipboard_warning">Se l\'eliminazione automatica degli appunti fallisce, cancellali manualmente.</string>
<string name="lock">Blocca</string> <string name="lock">Blocca</string>
<string name="lock_database_screen_off_title">Blocco schermo</string> <string name="lock_database_screen_off_title">Blocco schermo</string>
<string name="lock_database_screen_off_summary">Blocca la base di dati quando lo schermo è spento</string> <string name="lock_database_screen_off_summary">Blocca il database quando lo schermo è spento</string>
<string name="advanced_unlock">Impronta digitale</string> <string name="advanced_unlock">Impronta digitale</string>
<string name="biometric_unlock_enable_title">Scansione di impronte</string> <string name="biometric_unlock_enable_title">Scansione di impronte</string>
<string name="biometric_unlock_enable_summary">Consente la scansione biometrica per aprire il database</string> <string name="biometric_unlock_enable_summary">Consente la scansione biometrica per aprire il database</string>
<string name="biometric_delete_all_key_title">Elimina chiavi di cifratura</string> <string name="biometric_delete_all_key_title">Elimina chiavi di cifratura</string>
<string name="biometric_delete_all_key_summary">Elimina tutte le chiavi di cifratura relative al riconoscimento dell\'impronta</string> <string name="biometric_delete_all_key_summary">Elimina tutte le chiavi di cifratura relative allo sblocco avanzato</string>
<string name="unavailable_feature_text">Impossibile avviare questa funzione.</string> <string name="unavailable_feature_text">Impossibile avviare questa funzione.</string>
<string name="unavailable_feature_version">Il dispositivo usa Android %1$s, ma richiede %2$s o versioni successive.</string> <string name="unavailable_feature_version">Il dispositivo usa Android %1$s, ma richiede %2$s o versioni successive.</string>
<string name="unavailable_feature_hardware">L\'hardware relativo non è stato trovato.</string> <string name="unavailable_feature_hardware">L\'hardware relativo non è stato trovato.</string>
<string name="file_name">Nome file</string> <string name="file_name">Nome file</string>
<string name="path">Percorso</string> <string name="path">Percorso</string>
<string name="assign_master_key">Assegna una chiave master</string> <string name="assign_master_key">Assegna una chiave master</string>
<string name="create_keepass_file">Crea una nuova base di dati</string> <string name="create_keepass_file">Crea un nuovo database</string>
<string name="recycle_bin_title">Uso del Cestino</string> <string name="recycle_bin_title">Uso del Cestino</string>
<string name="recycle_bin_summary">Sposta i gruppi e le voci nel gruppo «Cestino» prima di eliminarlo</string> <string name="recycle_bin_summary">Sposta i gruppi e le voci nel gruppo «Cestino» prima di eliminarlo</string>
<string name="monospace_font_fields_enable_title">Carattere campi</string> <string name="monospace_font_fields_enable_title">Carattere campi</string>
@@ -227,32 +227,32 @@
<string name="allow_copy_password_title">Fiducia appunti</string> <string name="allow_copy_password_title">Fiducia appunti</string>
<string name="allow_copy_password_summary">Consenti la copia della password e dei campi protetti negli appunti</string> <string name="allow_copy_password_summary">Consenti la copia della password e dei campi protetti negli appunti</string>
<string name="allow_copy_password_warning">Attenzione: gli appunti sono condivisi da tutte le app. Se vengono copiati dati sensibili, altri software possono recuperarli.</string> <string name="allow_copy_password_warning">Attenzione: gli appunti sono condivisi da tutte le app. Se vengono copiati dati sensibili, altri software possono recuperarli.</string>
<string name="database_name_title">Nome della base di dati</string> <string name="database_name_title">Nome del database</string>
<string name="database_description_title">Descrizione della base di dati</string> <string name="database_description_title">Descrizione del database</string>
<string name="database_version_title">Versione della base di dati</string> <string name="database_version_title">Versione del database</string>
<string name="text_appearance">Testo</string> <string name="text_appearance">Testo</string>
<string name="application_appearance">App</string> <string name="application_appearance">Interfaccia</string>
<string name="other">Altro</string> <string name="other">Altro</string>
<string name="keyboard">Tastiera</string> <string name="keyboard">Tastiera</string>
<string name="magic_keyboard_title">Magitastiera</string> <string name="magic_keyboard_title">Magitastiera</string>
<string name="magic_keyboard_explanation_summary">Attiva una tastiera personale che popola le tue password e i campi di identità</string> <string name="magic_keyboard_explanation_summary">Attiva una tastiera personale che inserisce le tue password e i campi di identità</string>
<string name="allow_no_password_title">Non consentire nessuna chiave principale</string> <string name="allow_no_password_title">Non consentire nessuna chiave principale</string>
<string name="allow_no_password_summary">Permetti di toccare il pulsante \"Apri\" se non sono selezionate credenziali</string> <string name="allow_no_password_summary">Permetti di toccare il pulsante \"Apri\" se non sono selezionate credenziali</string>
<string name="enable_read_only_title">Protetto da scrittura</string> <string name="enable_read_only_title">Protetto da scrittura</string>
<string name="enable_read_only_summary">Apri la base di dati in sola lettura in modo predefinito</string> <string name="enable_read_only_summary">Apri il database in sola lettura in modo predefinito</string>
<string name="enable_education_screens_title">Suggerimenti educativi</string> <string name="enable_education_screens_title">Suggerimenti educativi</string>
<string name="enable_education_screens_summary">Evidenzia gli elementi per imparare come funziona l\'app</string> <string name="enable_education_screens_summary">Evidenzia gli elementi per imparare come funziona l\'app</string>
<string name="reset_education_screens_title">Ripristina i suggerimenti educativi</string> <string name="reset_education_screens_title">Ripristina i suggerimenti educativi</string>
<string name="reset_education_screens_summary">Mostra di nuovo tutte le informazioni educative</string> <string name="reset_education_screens_summary">Mostra di nuovo tutte le informazioni educative</string>
<string name="reset_education_screens_text">Suggerimenti educativi ripristinati</string> <string name="reset_education_screens_text">Suggerimenti educativi ripristinati</string>
<string name="education_create_database_title">Crea il tuo file di base di dati</string> <string name="education_create_database_title">Crea il tuo file database</string>
<string name="education_create_database_summary">Crea il tuo primo file di gestione password.</string> <string name="education_create_database_summary">Crea il tuo primo file di gestione password.</string>
<string name="education_select_database_title">Apri una base di dati esistente</string> <string name="education_select_database_title">Apri un database esitente</string>
<string name="education_select_database_summary">Apri il tuo file di base di dati precedente dal tuo gestore di file per continuare ad usarlo.</string> <string name="education_select_database_summary">Apri il tuo file database usato in precedenza con il file manager per continuare ad usarlo.</string>
<string name="education_new_node_title">Aggiungi elementi alla tua base di dati</string> <string name="education_new_node_title">Aggiungi elementi al tuo database</string>
<string name="education_new_node_summary">Gli elementi aiutano a gestire le tue identità digitali. <string name="education_new_node_summary">Gli elementi aiutano a gestire le tue identità digitali.
\n \n
\nI gruppi (come cartelle) organizzano gli elementi nella base di dati.</string> \nI gruppi (come cartelle) organizzano gli elementi nel database.</string>
<string name="education_search_title">Cerca tra gli elementi</string> <string name="education_search_title">Cerca tra gli elementi</string>
<string name="education_search_summary">Inserisci il titolo, il nome utente o il contenuto di altri campi per recuperare le tue password.</string> <string name="education_search_summary">Inserisci il titolo, il nome utente o il contenuto di altri campi per recuperare le tue password.</string>
<string name="education_entry_edit_title">Modifica l\'elemento</string> <string name="education_entry_edit_title">Modifica l\'elemento</string>
@@ -261,18 +261,18 @@
<string name="education_generate_password_summary">Genera una password robusta da associare all\'elemento, definiscila a seconda dei criteri del modulo e non dimenticare di tenerla al sicuro.</string> <string name="education_generate_password_summary">Genera una password robusta da associare all\'elemento, definiscila a seconda dei criteri del modulo e non dimenticare di tenerla al sicuro.</string>
<string name="education_entry_new_field_title">Aggiungi campi personalizzati</string> <string name="education_entry_new_field_title">Aggiungi campi personalizzati</string>
<string name="education_entry_new_field_summary">Registra un campo aggiuntivo, aggiungi un valore e facoltativamente proteggilo.</string> <string name="education_entry_new_field_summary">Registra un campo aggiuntivo, aggiungi un valore e facoltativamente proteggilo.</string>
<string name="education_unlock_title">Sblocca la tua base di dati</string> <string name="education_unlock_title">Sblocca il tuo database</string>
<string name="education_read_only_title">Proteggi da scrittura la tua base di dati</string> <string name="education_read_only_title">Proteggi da scrittura il tuo database</string>
<string name="education_read_only_summary">Cambia la modalità di apertura per la sessione. <string name="education_read_only_summary">Cambia la modalità di apertura per la sessione.
\n \n
\n«Sola lettura» impedisce modifiche accidentali alla base di dati. \n«Sola lettura» impedisce modifiche accidentali al databae.
\n«Modificabile» permette di aggiungere, eliminare o modificare tutti gli elementi.</string> \n«Modificabile» permette di aggiungere, eliminare o modificare tutti gli elementi.</string>
<string name="education_field_copy_title">Copia un campo</string> <string name="education_field_copy_title">Copia un campo</string>
<string name="education_field_copy_summary">I campi copiati possono essere incollati ovunque. <string name="education_field_copy_summary">I campi copiati possono essere incollati ovunque.
\n \n
\nUsa il metodo di inserimento che preferisci.</string> \nUsa il metodo di inserimento che preferisci.</string>
<string name="education_lock_title">Blocca la base di dati</string> <string name="education_lock_title">Blocca il database</string>
<string name="education_lock_summary">Blocca velocemente la base di dati, puoi impostare l\'applicazione per bloccarla dopo un certo periodo e quando lo schermo si spegne.</string> <string name="education_lock_summary">Blocca velocemente il database, puoi impostare l\'applicazione per bloccarsi dopo un certo periodo e quando lo schermo si spegne.</string>
<string name="education_sort_title">Ordine elementi</string> <string name="education_sort_title">Ordine elementi</string>
<string name="education_sort_summary">Scegli l\'ordine di elementi e gruppi.</string> <string name="education_sort_summary">Scegli l\'ordine di elementi e gruppi.</string>
<string name="education_donation_title">Partecipa</string> <string name="education_donation_title">Partecipa</string>
@@ -288,20 +288,19 @@
<string name="html_text_dev_feature_encourage">stai incoraggiando gli sviluppatori a creare &lt;strong&gt;nuove funzionalità&lt;/strong&gt; e a &lt;strong&gt;correggere errori&lt;/strong&gt; in base alle tue osservazioni.</string> <string name="html_text_dev_feature_encourage">stai incoraggiando gli sviluppatori a creare &lt;strong&gt;nuove funzionalità&lt;/strong&gt; e a &lt;strong&gt;correggere errori&lt;/strong&gt; in base alle tue osservazioni.</string>
<string name="html_text_dev_feature_thanks">Grazie mille per il tuo contributo.</string> <string name="html_text_dev_feature_thanks">Grazie mille per il tuo contributo.</string>
<string name="html_text_dev_feature_work_hard">Stiamo lavorando sodo per rilasciare questa funzione a breve.</string> <string name="html_text_dev_feature_work_hard">Stiamo lavorando sodo per rilasciare questa funzione a breve.</string>
<string name="html_text_dev_feature_upgrade">Non dimenticare di tenere aggiornata l\'app installando nuove versioni.</string> <string name="html_text_dev_feature_upgrade">Ricorda di tenere aggiornata l\'app installando le nuove versioni.</string>
<string name="download">Scarica</string> <string name="download">Scarica</string>
<string name="contribute">Contribuisci</string> <string name="contribute">Contribuisci</string>
<string name="encryption_rijndael">Rijndael (AES)</string> <string name="encryption_rijndael">Rijndael (AES)</string>
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Tema dell\'app</string> <string name="style_choose_title">Tema dell\'app</string>
<string name="style_choose_summary">Tema usato nell\'app</string> <string name="style_choose_summary">Tema usato nell\'app</string>
<string name="icon_pack_choose_title">Pacchetto icone</string> <string name="icon_pack_choose_title">Pacchetto icone</string>
<string name="icon_pack_choose_summary">Pacchetto icone usato nell\'app</string> <string name="icon_pack_choose_summary">Pacchetto icone usato nell\'app</string>
<string name="edit_entry">Modifica elemento</string> <string name="edit_entry">Modifica elemento</string>
<string name="error_load_database">Caricamento della base di dati fallito.</string> <string name="error_load_database">Caricamento del database fallito.</string>
<string name="error_load_database_KDF_memory">Caricamento della chiave fallito. Prova a diminuire l\'«Utilizzo memoria» del KDF.</string> <string name="error_load_database_KDF_memory">Caricamento della chiave fallito. Prova a diminuire l\'«Utilizzo memoria» del KDF.</string>
<string name="list_entries_show_username_title">Mostra nomi utente</string> <string name="list_entries_show_username_title">Mostra nomi utente</string>
<string name="list_entries_show_username_summary">Mostra i nomi utente negli elenchi</string> <string name="list_entries_show_username_summary">Mostra i nomi utente negli elenchi</string>
@@ -319,7 +318,7 @@
<string name="keyboard_notification_entry_content_title">%1$s disponibile nella Magitastiera</string> <string name="keyboard_notification_entry_content_title">%1$s disponibile nella Magitastiera</string>
<string name="keyboard_notification_entry_content_text">%1$s</string> <string name="keyboard_notification_entry_content_text">%1$s</string>
<string name="keyboard_notification_entry_clear_close_title">Pulisci alla chiusura</string> <string name="keyboard_notification_entry_clear_close_title">Pulisci alla chiusura</string>
<string name="keyboard_notification_entry_clear_close_summary">Chiudere la base di dati alla chiusura della notifica</string> <string name="keyboard_notification_entry_clear_close_summary">Chiudere il database alla chiusura della notifica</string>
<string name="keyboard_appearance_category">Aspetto</string> <string name="keyboard_appearance_category">Aspetto</string>
<string name="keyboard_theme_title">Tema tastiera</string> <string name="keyboard_theme_title">Tema tastiera</string>
<string name="keyboard_keys_category">Tasti</string> <string name="keyboard_keys_category">Tasti</string>
@@ -328,14 +327,14 @@
<string name="selection_mode">Modalità selezione</string> <string name="selection_mode">Modalità selezione</string>
<string name="do_not_kill_app">Non terminare l\'app…</string> <string name="do_not_kill_app">Non terminare l\'app…</string>
<string name="lock_database_back_root_title">Premere \'\'Indietro\'\' per bloccare</string> <string name="lock_database_back_root_title">Premere \'\'Indietro\'\' per bloccare</string>
<string name="lock_database_back_root_summary">Blocca la base di dati quando l\'utente preme il pulsante Indietro nella schermata principale</string> <string name="lock_database_back_root_summary">Blocca il database quando l\'utente preme il pulsante Indietro nella schermata principale</string>
<string name="clear_clipboard_notification_title">Pulisci alla chiusura</string> <string name="clear_clipboard_notification_title">Pulisci alla chiusura</string>
<string name="clear_clipboard_notification_summary">Blocca la base di dati quando scade la durata degli appunti o la notifica viene chiusa dopo che inizi ad usarlo</string> <string name="clear_clipboard_notification_summary">Blocca il database quando scade la durata degli appunti o la notifica viene chiusa dopo che inizi ad usarlo</string>
<string name="recycle_bin">Cestino</string> <string name="recycle_bin">Cestino</string>
<string name="keyboard_selection_entry_title">Selezione elemento</string> <string name="keyboard_selection_entry_title">Selezione elemento</string>
<string name="keyboard_selection_entry_summary">Mostra i campi di input nella Magitastiera durante la visualizzazione di un elemento</string> <string name="keyboard_selection_entry_summary">Mostra i campi di input nella Magitastiera durante la visualizzazione di un elemento</string>
<string name="delete_entered_password_title">Elimina password</string> <string name="delete_entered_password_title">Elimina password</string>
<string name="delete_entered_password_summary">Elimina la password immessa dopo un tentativo di connessione alla base di dati</string> <string name="delete_entered_password_summary">Elimina la password immessa dopo un tentativo di connessione al database</string>
<string name="content_description_open_file">Apri il file</string> <string name="content_description_open_file">Apri il file</string>
<string name="content_description_node_children">Figli del nodo</string> <string name="content_description_node_children">Figli del nodo</string>
<string name="content_description_add_node">Aggiungi un nodo</string> <string name="content_description_add_node">Aggiungi un nodo</string>
@@ -359,7 +358,7 @@
<string name="security">Sicurezza</string> <string name="security">Sicurezza</string>
<string name="content_description_background">Sfondo</string> <string name="content_description_background">Sfondo</string>
<string name="entry_UUID">Identificativo univoco universale</string> <string name="entry_UUID">Identificativo univoco universale</string>
<string name="error_create_database_file">Impossibile creare una base di dati con questa password e file chiave.</string> <string name="error_create_database_file">Impossibile creare un database con questa password e file chiave.</string>
<string name="menu_advanced_unlock_settings">Sblocco avanzato</string> <string name="menu_advanced_unlock_settings">Sblocco avanzato</string>
<string name="entry_history">Cronologia</string> <string name="entry_history">Cronologia</string>
<string name="entry_setup_otp">Imposta password usa e getta</string> <string name="entry_setup_otp">Imposta password usa e getta</string>
@@ -378,16 +377,16 @@
<string name="error_otp_period">Il periodo deve essere tra %1$d e %2$d secondi.</string> <string name="error_otp_period">Il periodo deve essere tra %1$d e %2$d secondi.</string>
<string name="error_otp_digits">Il token deve contenere tra %1$d e %2$d cifre.</string> <string name="error_otp_digits">Il token deve contenere tra %1$d e %2$d cifre.</string>
<string name="invalid_db_same_uuid">%1$s con le stesse credenziali univoche %2$s è già esistente.</string> <string name="invalid_db_same_uuid">%1$s con le stesse credenziali univoche %2$s è già esistente.</string>
<string name="creating_database">Sto creando la base di dati</string> <string name="creating_database">Sto creando il database</string>
<string name="menu_security_settings">Impostazioni di sicurezza</string> <string name="menu_security_settings">Impostazioni di sicurezza</string>
<string name="contains_duplicate_uuid">Il database contiene Identificativi Univoci Universali (UUID) duplicati.</string> <string name="contains_duplicate_uuid">Il database contiene Identificativi Univoci Universali (UUID) duplicati.</string>
<string name="error_save_database">Non è possibile salvare la base di dati.</string> <string name="error_save_database">Non è possibile salvare il database.</string>
<string name="menu_save_database">Salva la base di dati</string> <string name="menu_save_database">Salva database</string>
<string name="menu_empty_recycle_bin">Svuota il cestino</string> <string name="menu_empty_recycle_bin">Svuota il cestino</string>
<string name="command_execution">Esecuzione del comando…</string> <string name="command_execution">Esecuzione del comando…</string>
<string name="warning_permanently_delete_nodes">Vuoi eliminare definitivamente i nodi selezionati\?</string> <string name="warning_permanently_delete_nodes">Vuoi eliminare definitivamente i nodi selezionati\?</string>
<string name="entry_attachments">Allegati</string> <string name="entry_attachments">Allegati</string>
<string name="auto_focus_search_summary">Richiedi una ricerca quando una base di dati viene aperta</string> <string name="auto_focus_search_summary">Richiedi una ricerca quando un database viene aperto</string>
<string name="auto_focus_search_title">Ricerca rapida</string> <string name="auto_focus_search_title">Ricerca rapida</string>
<string name="menu_delete_entry_history">Cancella cronologia</string> <string name="menu_delete_entry_history">Cancella cronologia</string>
<string name="menu_restore_entry_history">Ripristina cronologia</string> <string name="menu_restore_entry_history">Ripristina cronologia</string>
@@ -397,46 +396,46 @@
<string name="menu_master_key_settings">Impostazioni della chiave principale</string> <string name="menu_master_key_settings">Impostazioni della chiave principale</string>
<string name="master_key">Chiave principale</string> <string name="master_key">Chiave principale</string>
<string name="contribution">Contributi</string> <string name="contribution">Contributi</string>
<string name="warning_database_read_only">Garantisci il permesso di scrittura per salvare i cambiamenti della base di dati</string> <string name="warning_database_read_only">Concedi il permesso di scrittura per salvare i cambiamenti del database</string>
<string name="hide_broken_locations_summary">Nascondi collegamenti corrotti nella lista delle basi di dati recenti</string> <string name="hide_broken_locations_summary">Nascondi collegamenti corrotti nella lista dei database recenti</string>
<string name="hide_broken_locations_title">Nascondi i collegamenti di basi di dati corrotti</string> <string name="hide_broken_locations_title">Nascondi i collegamenti dei database corrotti</string>
<string name="show_recent_files_summary">Mostra le posizioni delle basi di dati recenti</string> <string name="show_recent_files_summary">Mostra le posizioni dei database recenti</string>
<string name="show_recent_files_title">Mostra file recenti</string> <string name="show_recent_files_title">Mostra file recenti</string>
<string name="remember_keyfile_locations_title">Ricorda la posizione dei file chiave</string> <string name="remember_keyfile_locations_title">Ricorda posizione file chiave</string>
<string name="remember_database_locations_summary">Ricorda la posizione delle basi di dati</string> <string name="remember_database_locations_summary">Ricorda la posizione dei database</string>
<string name="remember_database_locations_title">Ricorda la posizione delle basi di dati</string> <string name="remember_database_locations_title">Ricorda posizione database</string>
<string name="contains_duplicate_uuid_procedure">Per continuare, risolvi il problema generando nuovi UUID per i duplicati\?</string> <string name="contains_duplicate_uuid_procedure">Risolvi il problema generando nuovi UUID per i duplicati per continuare\?</string>
<string name="error_create_database">Impossibile creare il file della base di dati.</string> <string name="error_create_database">Impossibile creare il file del database.</string>
<string name="entry_add_attachment">Aggiungi allegato</string> <string name="entry_add_attachment">Aggiungi allegato</string>
<string name="discard">Scarta</string> <string name="discard">Scarta</string>
<string name="discard_changes">Scartare i cambiamenti\?</string> <string name="discard_changes">Scartare i cambiamenti\?</string>
<string name="validate">Convalida</string> <string name="validate">Convalida</string>
<string name="max_history_size_title">Dimensione massima</string> <string name="max_history_size_title">Dimensione massima</string>
<string name="max_history_items_title">Numero massimo</string> <string name="max_history_items_title">Numero massimo</string>
<string name="biometric_auto_open_prompt_title">Apri automaticamente prompt biometrico</string> <string name="biometric_auto_open_prompt_title">Apri automaticamente la richiesta</string>
<string name="max_history_size_summary">Limita la dimensione (in byte) della cronologia per voce</string> <string name="max_history_size_summary">Limita la dimensione (in byte) della cronologia per voce</string>
<string name="max_history_items_summary">Limita il numero di elementi della cronologia per voce</string> <string name="max_history_items_summary">Limita il numero di elementi della cronologia per voce</string>
<string name="recycle_bin_group_title">Gruppo cestino</string> <string name="recycle_bin_group_title">Gruppo cestino</string>
<string name="database_data_compression_summary">La compressione dei dati riduce le dimensioni della base di dati</string> <string name="database_data_compression_summary">La compressione dei dati riduce le dimensioni del database</string>
<string name="database_data_compression_title">Compressione dati</string> <string name="database_data_compression_title">Compressione dati</string>
<string name="biometric_auto_open_prompt_summary">Proponi l\'autenticazione biometrica quando la base di dati è configurata per usarla</string> <string name="biometric_auto_open_prompt_summary">Richiedi automaticamente lo sblocco avanzato se il database è impostato per usarlo</string>
<string name="advanced_unlock_explanation_summary">Utilizza lo sblocco avanzato per aprire la base di dati più facilmente</string> <string name="advanced_unlock_explanation_summary">Utilizza lo sblocco avanzato per aprire il database più facilmente</string>
<string name="clipboard_explanation_summary">Copia i campi di immissione utilizzando gli appunti del tuo dispositivo</string> <string name="clipboard_explanation_summary">Copia i campi di immissione utilizzando gli appunti del tuo dispositivo</string>
<string name="database_opened">Base di dati aperta</string> <string name="database_opened">Database aperto</string>
<string name="biometric">Biometrico</string> <string name="biometric">Biometrico</string>
<string name="settings_database_force_changing_master_key_title">Forza rinnovo</string> <string name="settings_database_force_changing_master_key_title">Forza rinnovo</string>
<string name="settings_database_recommend_changing_master_key_summary">È consigliato di cambiare la chiave principale (giorni)</string> <string name="settings_database_recommend_changing_master_key_summary">È consigliato di cambiare la chiave principale (giorni)</string>
<string name="settings_database_recommend_changing_master_key_title">Rinnovo raccomandato</string> <string name="settings_database_recommend_changing_master_key_title">Rinnovo raccomandato</string>
<string name="hide_expired_entries_summary">Le voci scadute non sono mostrate</string> <string name="hide_expired_entries_summary">Le voci scadute non sono mostrate</string>
<string name="hide_expired_entries_title">Nascondi le voci scadute</string> <string name="hide_expired_entries_title">Nascondi le voci scadute</string>
<string name="education_setup_OTP_summary">Imposta la gestione delle OTP (HOTP / TOTP) per generare un token richiesto per la 2FA.</string> <string name="education_setup_OTP_summary">Imposta la gestione della password usa e getta (HOTP/TOTP) per generare un token richiesto per l\'autenticazione a due fattori.</string>
<string name="download_complete">Completato!</string> <string name="download_complete">Completato!</string>
<string name="download_finalization">Finalizzazione…</string> <string name="download_finalization">Finalizzazione…</string>
<string name="download_progression">Avanzamento %1$d%%</string> <string name="download_progression">Avanzamento %1$d%%</string>
<string name="download_initialization">Inizializzazione…</string> <string name="download_initialization">Inizializzazione…</string>
<string name="download_attachment">Scaricamento %1$s</string> <string name="download_attachment">Scaricamento %1$s</string>
<string name="education_setup_OTP_title">Imposta One-Time Password (OTP)</string> <string name="education_setup_OTP_title">Imposta One-Time Password (OTP)</string>
<string name="enable_auto_save_database_summary">Salva la base di dati dopo ogni azione importante (in modalità «Modificabile»)</string> <string name="enable_auto_save_database_summary">Salva il database dopo ogni azione importante (in modalità «Modificabile»)</string>
<string name="enable_auto_save_database_title">Salvataggio automatico del database</string> <string name="enable_auto_save_database_title">Salvataggio automatico del database</string>
<string name="autofill_auto_search_summary">Suggerisci automaticamente risultati dal dominio web o ID dell\'applicazione</string> <string name="autofill_auto_search_summary">Suggerisci automaticamente risultati dal dominio web o ID dell\'applicazione</string>
<string name="autofill_auto_search_title">Ricerca automatica</string> <string name="autofill_auto_search_title">Ricerca automatica</string>
@@ -446,18 +445,18 @@
<string name="compression_gzip">Gzip</string> <string name="compression_gzip">Gzip</string>
<string name="compression_none">Nessuna</string> <string name="compression_none">Nessuna</string>
<string name="compression">Compressione</string> <string name="compression">Compressione</string>
<string name="database_custom_color_title">Colore personalizzato della base di dati</string> <string name="database_custom_color_title">Colore personalizzato del database</string>
<string name="database_default_username_title">Nome utente predefinito</string> <string name="database_default_username_title">Nome utente predefinito</string>
<string name="disable">Disabilita</string> <string name="disable">Disabilita</string>
<string name="enable">Abilita</string> <string name="enable">Abilita</string>
<string name="settings_database_force_changing_master_key_next_time_summary">Richiedi il cambio della chiave principale la prossima volta (una volta)</string> <string name="settings_database_force_changing_master_key_next_time_summary">Richiedi il cambio della chiave principale la prossima volta (una volta)</string>
<string name="settings_database_force_changing_master_key_next_time_title">Forza il rinnovo la prossima volta</string> <string name="settings_database_force_changing_master_key_next_time_title">Forza il rinnovo la prossima volta</string>
<string name="settings_database_force_changing_master_key_summary">Richiedi il cambio della master key (giorni)</string> <string name="settings_database_force_changing_master_key_summary">Richiedi la modifica della chiave principale (giorni)</string>
<string name="lock_database_show_button_summary">Mostra il bottone di blocco nell\'interfaccia utente</string> <string name="lock_database_show_button_summary">Mostra il bottone di blocco nell\'interfaccia utente</string>
<string name="lock_database_show_button_title">Mostra il bottone di blocco</string> <string name="lock_database_show_button_title">Mostra il bottone di blocco</string>
<string name="autofill_preference_title">Impostazioni dell\'autocompletamento</string> <string name="autofill_preference_title">Impostazioni dell\'autocompletamento</string>
<string name="warning_database_link_revoked">Accesso al file revocato dal file manager</string> <string name="warning_database_link_revoked">Accesso al file revocato dal file manager</string>
<string name="remember_keyfile_locations_summary">Ricorda la posizione dei file chiave dei basi di dati</string> <string name="remember_keyfile_locations_summary">Ricorda la posizione dei file chiave</string>
<string name="error_label_exists">Questa etichetta esiste già.</string> <string name="error_label_exists">Questa etichetta esiste già.</string>
<string name="autofill_block_restart">Riavvia l\'app contenente il campo per attivare il blocco.</string> <string name="autofill_block_restart">Riavvia l\'app contenente il campo per attivare il blocco.</string>
<string name="autofill_block">Blocca riempimento automatico</string> <string name="autofill_block">Blocca riempimento automatico</string>
@@ -474,24 +473,24 @@
<string name="content_description_add_item">Aggiungi elemento</string> <string name="content_description_add_item">Aggiungi elemento</string>
<string name="keyboard_previous_fill_in_summary">Torna automaticamente alla tastiera precedente quando si esegue l\'azione del tasto automatico</string> <string name="keyboard_previous_fill_in_summary">Torna automaticamente alla tastiera precedente quando si esegue l\'azione del tasto automatico</string>
<string name="keyboard_previous_fill_in_title">Azione tasto automatico</string> <string name="keyboard_previous_fill_in_title">Azione tasto automatico</string>
<string name="keyboard_previous_database_credentials_summary">Torna automaticamente alla tastiera precedente nella schermata delle credenziali della base di dati</string> <string name="keyboard_previous_database_credentials_summary">Torna automaticamente alla tastiera precedente nella schermata credenziali del database</string>
<string name="keyboard_previous_database_credentials_title">Schermata credenziali della base di dati</string> <string name="keyboard_previous_database_credentials_title">Schermata credenziali del database</string>
<string name="keyboard_change">Cambia tastiera</string> <string name="keyboard_change">Cambia tastiera</string>
<string name="upload_attachment">Carica %1$s</string> <string name="upload_attachment">Carica %1$s</string>
<string name="education_add_attachment_summary">Carica un allegato alla voce per salvare dati esterni importanti.</string> <string name="education_add_attachment_summary">Carica un allegato alla voce per salvare dati esterni importanti.</string>
<string name="education_add_attachment_title">Aggiungi allegato</string> <string name="education_add_attachment_title">Aggiungi allegato</string>
<string name="database_data_remove_unlinked_attachments_summary">Rimuovi gli allegati contenuti nella base di dati ma non collegati ad una voce</string> <string name="database_data_remove_unlinked_attachments_summary">Rimuovi gli allegati contenuti nel database ma non collegati ad una voce</string>
<string name="database_data_remove_unlinked_attachments_title">Rimuovi i dati scollegati</string> <string name="database_data_remove_unlinked_attachments_title">Rimuovi i dati scollegati</string>
<string name="data">Dati</string> <string name="data">Dati</string>
<string name="warning_empty_keyfile_explanation">Il contenuto del file chiave non deve mai essere modificato e, nel migliore dei casi, dovrebbe contenere dati generati casualmente.</string> <string name="warning_empty_keyfile_explanation">Il contenuto del file chiave non deve mai essere modificato e, nel migliore dei casi, dovrebbe contenere dati generati casualmente.</string>
<string name="warning_empty_keyfile">Non è consigliabile aggiungere un file chiave vuoto.</string> <string name="warning_empty_keyfile">Non è consigliabile aggiungere un file chiave vuoto.</string>
<string name="warning_sure_remove_data">Rimuovere questi dati comunque\?</string> <string name="warning_sure_remove_data">Rimuovere questi dati comunque\?</string>
<string name="warning_remove_unlinked_attachment">La rimozione di dati scollegati può ridurre le dimensioni della base di dati, ma può anche eliminare i dati utilizzati per i plugin KeePass.</string> <string name="warning_remove_unlinked_attachment">La rimozione di dati scollegati può ridurre le dimensioni del database, ma può anche eliminare i dati utilizzati per i plugin KeePass.</string>
<string name="warning_sure_add_file">Aggiungi comunque il file\?</string> <string name="warning_sure_add_file">Aggiungi comunque il file\?</string>
<string name="warning_replace_file">Caricare questo file sostituirà quello esistente.</string> <string name="warning_replace_file">Caricare questo file sostituirà quello esistente.</string>
<string name="warning_file_too_big">Una base di dati KeePass dovrebbe contenere solo piccoli file di utilità (come i file di chiavi PGP). <string name="warning_file_too_big">Un database KeePass dovrebbe contenere solo piccoli file di utilità (come i file di chiavi PGP).
\n \n
\nLa base di dati può diventare molto grande e ridurre le prestazioni con questo caricamento.</string> \nIl tuo database può diventare molto grande e ridurre le prestazioni con questo caricamento.</string>
<string name="content_description_credentials_information">Info credenziali</string> <string name="content_description_credentials_information">Info credenziali</string>
<string name="show_uuid_summary">Visualizza l\'UUID collegato a una voce</string> <string name="show_uuid_summary">Visualizza l\'UUID collegato a una voce</string>
<string name="show_uuid_title">Mostra UUID</string> <string name="show_uuid_title">Mostra UUID</string>
@@ -500,20 +499,60 @@
<string name="autofill_ask_to_save_data_title">Chiedi di salvare i dati</string> <string name="autofill_ask_to_save_data_title">Chiedi di salvare i dati</string>
<string name="autofill_save_search_info_summary">Prova a salvare le informazioni di ricerca quando effettui una selezione di immissione manuale</string> <string name="autofill_save_search_info_summary">Prova a salvare le informazioni di ricerca quando effettui una selezione di immissione manuale</string>
<string name="autofill_save_search_info_title">Salva le informazioni di ricerca</string> <string name="autofill_save_search_info_title">Salva le informazioni di ricerca</string>
<string name="autofill_close_database_summary">Chiudi la base di dati dopo una selezione di compilazione automatica</string> <string name="autofill_close_database_summary">Chiudi il database dopo aver usato l\'autocompletamento</string>
<string name="autofill_close_database_title">Chiudi la base di dati</string> <string name="autofill_close_database_title">Chiudi database</string>
<string name="keyboard_previous_lock_summary">Torna automaticamente alla tastiera precedente dopo aver bloccato la base di dati</string> <string name="keyboard_previous_lock_summary">Torna automaticamente alla tastiera precedente dopo aver bloccato il database</string>
<string name="keyboard_previous_lock_title">Blocca la base di dati</string> <string name="keyboard_previous_lock_title">Blocca il database</string>
<string name="keyboard_save_search_info_summary">Prova a salvare le informazioni condivise quando effettui una selezione di immissione manuale</string> <string name="keyboard_save_search_info_summary">Prova a salvare le informazioni condivise quando effettui una selezione di immissione manuale</string>
<string name="keyboard_save_search_info_title">Salva le informazioni condivise</string> <string name="keyboard_save_search_info_title">Salva le informazioni condivise</string>
<string name="notification">Notifica</string> <string name="notification">Notifica</string>
<string name="crypto_object_not_initialized">Impossibile recuperare l\'oggetto crittografico.</string>
<string name="biometric_security_update_required">È necessario un aggiornamento della sicurezza biometrica.</string> <string name="biometric_security_update_required">È necessario un aggiornamento della sicurezza biometrica.</string>
<string name="warning_empty_recycle_bin">Eliminare definitivamente tutti i nodi dal cestino\?</string> <string name="warning_empty_recycle_bin">Eliminare definitivamente tutti i nodi dal cestino\?</string>
<string name="registration_mode">Modalità registrazione</string> <string name="registration_mode">Modalità registrazione</string>
<string name="save_mode">Modalità salvataggio</string> <string name="save_mode">Modalità salvataggio</string>
<string name="search_mode">Modalità ricerca</string> <string name="search_mode">Modalità ricerca</string>
<string name="error_field_name_already_exists">Il nome del campo esiste già.</string> <string name="error_field_name_already_exists">Il nome del campo esiste già.</string>
<string name="error_registration_read_only">Il salvataggio di un nuovo elemento non è consentito in una base di dati di sola lettura</string> <string name="error_registration_read_only">Il salvataggio di un nuovo elemento non è consentito in un database di sola lettura</string>
<string name="configure_biometric">Nessuna credenziale biometrica o del dispositivo è registrata.</string> <string name="configure_biometric">Nessuna credenziale biometrica o del dispositivo è registrata.</string>
<string name="education_advanced_unlock_summary">Collega la password alla tua autenticazione biometrica (o del dispositivo) per sbloccare velocemente il database.</string>
<string name="education_advanced_unlock_title">Sblocco avanzato del database</string>
<string name="enter">Invio</string>
<string name="backspace">Backspace</string>
<string name="select_entry">Seleziona voce</string>
<string name="back_to_previous_keyboard">Torna alla tasitera precedente</string>
<string name="custom_fields">Campi personalizzati</string>
<string name="advanced_unlock_delete_all_key_warning">Vuoi eliminare le chiavi di cifratura relative allo sblocco avanzato\?</string>
<string name="advanced_unlock_timeout">Scadenza sblocco avanzato</string>
<string name="temp_advanced_unlock_enable_summary">Non salvare alcun contenuto criptato per usare lo sblocco avanzato</string>
<string name="temp_advanced_unlock_timeout_summary">Validità dello sblocco avanzato prima di eliminarne il contenuto</string>
<string name="temp_advanced_unlock_timeout_title">Scadenza sblocco avanzato</string>
<string name="temp_advanced_unlock_enable_title">Sblocco avanzato temporaneo</string>
<string name="device_credential_unlock_enable_summary">Utilizza le credenziali del dispositivo per sbloccare il database</string>
<string name="device_credential_unlock_enable_title">Sblocco con credenziali dispositivo</string>
<string name="advanced_unlock_tap_delete">Tocca per eliminare le chiavi di sblocco avanzato</string>
<string name="content">Contenuto</string>
<string name="advanced_unlock_prompt_not_initialized">Non è possibile inizializzare lo sblocco avanzato.</string>
<string name="advanced_unlock_not_recognized">Non è possibile riconoscere lo sblocco avanzato</string>
<string name="advanced_unlock_invalid_key">Non è possibile leggere la chiave di sblocco avanzato. Eliminala e ripeti la prodecura dello sblocco.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Estrai le credenziali del database con i dati dallo sblocco avanzato</string>
<string name="advanced_unlock_prompt_store_credential_message">Attenzione: dovrai sempre ricordare la password principale anche se usi lo sblocco avanzato.</string>
<string name="advanced_unlock_prompt_store_credential_title">Riconoscimento con sblocco avanzato</string>
<string name="device_credential">Credenziali del dispositivo</string>
<string name="credential_before_click_advanced_unlock_button">Inserisci la password, quindi clicca sull\'icona \"Sblocco avanzato\".</string>
<string name="advanced_unlock_scanning_error">Errore sblocco avanzato: %1$s</string>
<string name="advanced_unlock_prompt_extract_credential_title">Apri il database con lo sblocco avanzato</string>
<string name="open_advanced_unlock_prompt_unlock_database">Autentica con lo sblocco avanzato per sbloccare il database</string>
<string name="open_advanced_unlock_prompt_store_credential">Autentica con lo sblocco avanzato per salvare le credenziali</string>
<string name="menu_keystore_remove_key">Elimina chiave di sblocco avanzato</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_rebuild_list">Non è possibile ricostruire la lista correttamente.</string>
<string name="error_database_uri_null">Non è stato recuperato l\'indirizzo del database.</string>
<string name="autofill_inline_suggestions_keyboard">Suggerimento di riempimento aggiunto.</string>
<string name="autofill_inline_suggestions_summary">Mostra i suggerimenti di riempimento campi in una tastiera compatibile</string>
<string name="autofill_inline_suggestions_title">Suggerimenti in linea</string>
<string name="warning_database_revoked">L\'accesso al file è stato revocato dal file manager, chiudi il database e riaprilo dalla sua posizione originale.</string>
<string name="warning_database_info_changed_options">Sovrascrivi le modifiche esterne salvano il database o ricaricalo con gli ultimi cambiamenti.</string>
<string name="warning_database_info_changed">I dati nel tuo database sono stati modificati al di fuori di questa app.</string>
<string name="menu_reload_database">Ricarica database</string>
</resources> </resources>

View File

@@ -273,7 +273,6 @@
<string name="keystore_not_accessible">キーストアが正しく初期化されていません。</string> <string name="keystore_not_accessible">キーストアが正しく初期化されていません。</string>
<string name="encrypted_value_stored">保存された暗号化済みパスワード</string> <string name="encrypted_value_stored">保存された暗号化済みパスワード</string>
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string> <string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
<string name="crypto_object_not_initialized">crypto オブジェクトを取得できません。</string>
<string name="database_history">履歴</string> <string name="database_history">履歴</string>
<string name="menu_appearance_settings">デザイン</string> <string name="menu_appearance_settings">デザイン</string>
<string name="biometric">生体認証</string> <string name="biometric">生体認証</string>
@@ -475,7 +474,6 @@
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string-array name="timeout_options"> <string-array name="timeout_options">
<item>5秒</item> <item>5秒</item>
<item>10秒</item> <item>10秒</item>
@@ -543,4 +541,7 @@
<string name="temp_advanced_unlock_enable_title">一時的な高度なロック解除</string> <string name="temp_advanced_unlock_enable_title">一時的な高度なロック解除</string>
<string name="advanced_unlock_tap_delete">タップして高度なロック解除用の鍵を削除する</string> <string name="advanced_unlock_tap_delete">タップして高度なロック解除用の鍵を削除する</string>
<string name="content">コンテンツ</string> <string name="content">コンテンツ</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_database_uri_null">データベースのURIが見つかりません。</string>
</resources> </resources>

View File

@@ -192,7 +192,6 @@
<string name="extended_ASCII">വിപുലീകരിച്ച ASCII</string> <string name="extended_ASCII">വിപുലീകരിച്ച ASCII</string>
<string name="icon_pack_choose_summary">ആപ്പിൽ ഉപയോഗിച്ചിരിക്കുന്ന ഐക്കൺ പാക്ക്</string> <string name="icon_pack_choose_summary">ആപ്പിൽ ഉപയോഗിച്ചിരിക്കുന്ന ഐക്കൺ പാക്ക്</string>
<string name="style_choose_title">അപ്പ്ലിക്കേഷന്റെ തീം</string> <string name="style_choose_title">അപ്പ്ലിക്കേഷന്റെ തീം</string>
<string name="kdf_Argon2">Argon2</string>
<string name="download_finalization">അന്തിമമാക്കുന്നു. . .</string> <string name="download_finalization">അന്തിമമാക്കുന്നു. . .</string>
<string name="html_text_dev_feature_thanks">നിങ്ങളുടെ സംഭാവനയ്ക്ക് ഒരുപാട് നന്ദി.</string> <string name="html_text_dev_feature_thanks">നിങ്ങളുടെ സംഭാവനയ്ക്ക് ഒരുപാട് നന്ദി.</string>
<string name="education_unlock_title">ഡാറ്റാബേസ് തുറക്കുക</string> <string name="education_unlock_title">ഡാറ്റാബേസ് തുറക്കുക</string>
@@ -389,4 +388,12 @@
<string name="registration_mode">രജിസ്ട്രേഷൻ മോഡ്</string> <string name="registration_mode">രജിസ്ട്രേഷൻ മോഡ്</string>
<string name="search_mode">തിരയൽ മോഡ്</string> <string name="search_mode">തിരയൽ മോഡ്</string>
<string name="error_registration_read_only">read-only ഡാറ്റാബേസിൽ പുതിയ ഒരു ഇനം സംരക്ഷിക്കാൻ കഴിയില്ല</string> <string name="error_registration_read_only">read-only ഡാറ്റാബേസിൽ പുതിയ ഒരു ഇനം സംരക്ഷിക്കാൻ കഴിയില്ല</string>
<string name="education_advanced_unlock_title">വിപുലമായ ഡാറ്റാബേസ് അൺലോക്ക്</string>
<string name="select_entry">എൻ‌ട്രി തിരഞ്ഞെടുക്കുക</string>
<string name="custom_fields">ഇഷ്‌ടാനുസൃത ഫീൽഡുകൾ</string>
<string name="keyboard_keys_category">കീകൾ</string>
<string name="notification">അറിയിപ്പ്</string>
<string name="settings_database_recommend_changing_master_key_title">പുതുക്കൽ ശുപാർശ ചെയ്യുക</string>
<string name="content">ഉള്ളടക്കം</string>
<string name="device_credential">ഉപകരണ ക്രെഡൻഷ്യൽ</string>
</resources> </resources>

View File

@@ -284,7 +284,6 @@
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Velg en drakt</string> <string name="style_choose_title">Velg en drakt</string>
<string name="style_choose_summary">Tilpass programdrakten ved å endre fargene</string> <string name="style_choose_summary">Tilpass programdrakten ved å endre fargene</string>
<string name="icon_pack_choose_title">Velg en ikonpakke</string> <string name="icon_pack_choose_title">Velg en ikonpakke</string>
@@ -418,7 +417,7 @@
<string name="hide_broken_locations_summary">Skjul ødelagte lenker i listen over nylige databaser</string> <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="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="autofill_ask_to_save_data_title">Spør om lagring av data</string>
<string name="advanced_unlock_scanning_error">Avansert opplåsningsfeil:</string> <string name="advanced_unlock_scanning_error">Avansert opplåsningsfeil: %1$s</string>
<string name="warning_empty_keyfile">Det anbefales ikke å legge til en tom nøkkelfil.</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="warning_sure_add_file">Legg til filen uansett\?</string>
<string name="registration_mode">Registreringsmodus</string> <string name="registration_mode">Registreringsmodus</string>

View File

@@ -34,7 +34,7 @@
<string name="file_manager_install_description">Bestandsbeheer dat de Intent-actie ACTION_CREATE_DOCUMENT en ACTION_OPEN_DOCUMENT accepteert, is nodig om databasebestanden aan te maken, te openen en op te slaan.</string> <string name="file_manager_install_description">Bestandsbeheer dat de Intent-actie ACTION_CREATE_DOCUMENT en ACTION_OPEN_DOCUMENT accepteert, is nodig om databasebestanden aan te maken, te openen en op te slaan.</string>
<string name="clipboard_cleared">Klembord gewist</string> <string name="clipboard_cleared">Klembord gewist</string>
<string name="clipboard_timeout">Klembordtime-out</string> <string name="clipboard_timeout">Klembordtime-out</string>
<string name="clipboard_timeout_summary">Tijd van opslag op het klembord (indien ondersteund op jouw apparaat)</string> <string name="clipboard_timeout_summary">Tijd van opslag op het klembord (voor zover ondersteund op dit apparaat)</string>
<string name="select_to_copy">Selecteer om %1$s naar klembord te kopiëren</string> <string name="select_to_copy">Selecteer om %1$s naar klembord te kopiëren</string>
<string name="retrieving_db_key">Databasesleutel ophalen…</string> <string name="retrieving_db_key">Databasesleutel ophalen…</string>
<string name="database">Database</string> <string name="database">Database</string>
@@ -67,7 +67,7 @@
<string name="error_out_of_memory">Onvoldoende vrij geheugen om de gehele database te laden.</string> <string name="error_out_of_memory">Onvoldoende vrij geheugen om de gehele database te laden.</string>
<string name="error_pass_gen_type">Je moet minimaal één soort wachtwoordgenerering kiezen.</string> <string name="error_pass_gen_type">Je moet minimaal één soort wachtwoordgenerering kiezen.</string>
<string name="error_pass_match">De wachtwoorden komen niet overeen.</string> <string name="error_pass_match">De wachtwoorden komen niet overeen.</string>
<string name="error_rounds_too_large">\"Cycli-waarde\" te groot. Wordt ingesteld op 2147483648.</string> <string name="error_rounds_too_large">\"Cycli-waarde\" te groot. Deze wordt ingesteld op 2147483648.</string>
<string name="error_wrong_length">Voer een positief geheel getal in in het veld \"Lengte\".</string> <string name="error_wrong_length">Voer een positief geheel getal in in het veld \"Lengte\".</string>
<string name="file_browser">Bestandsbeheer</string> <string name="file_browser">Bestandsbeheer</string>
<string name="generate_password">Wachtwoord genereren</string> <string name="generate_password">Wachtwoord genereren</string>
@@ -81,16 +81,16 @@
<string name="invalid_credentials">Kan referenties niet lezen.</string> <string name="invalid_credentials">Kan referenties niet lezen.</string>
<string name="invalid_db_sig">Databaseformaat kan niet worden herkend.</string> <string name="invalid_db_sig">Databaseformaat kan niet worden herkend.</string>
<string name="length">Lengte</string> <string name="length">Lengte</string>
<string name="list_size_title">Lengte van lijst met items</string> <string name="list_size_title">Lijstgrootte</string>
<string name="list_size_summary">Tekstgrootte in de lijst</string> <string name="list_size_summary">Tekstgrootte in de itemslijst</string>
<string name="loading_database">Database laden…</string> <string name="loading_database">Database laden…</string>
<string name="lowercase">Kleine letters</string> <string name="lowercase">Kleine letters</string>
<string name="hide_password_title">Wachtwoorden verbergen</string> <string name="hide_password_title">Wachtwoorden verbergen</string>
<string name="hide_password_summary">Wachtwoorden standaard verbergen (***)</string> <string name="hide_password_summary">Wachtwoorden standaard maskeren (***)</string>
<string name="about">Over</string> <string name="about">Over</string>
<string name="menu_change_key_settings">Hoofdsleutel wijzigen</string> <string name="menu_change_key_settings">Hoofdsleutel wijzigen</string>
<string name="settings">Instellingen</string> <string name="settings">Instellingen</string>
<string name="menu_database_settings">Instellingen database</string> <string name="menu_database_settings">Database-instellingen</string>
<string name="menu_delete">Verwijderen</string> <string name="menu_delete">Verwijderen</string>
<string name="menu_donate">Doneren</string> <string name="menu_donate">Doneren</string>
<string name="menu_edit">Bewerken</string> <string name="menu_edit">Bewerken</string>
@@ -205,15 +205,15 @@
<string name="autofill">Auto-aanvullen</string> <string name="autofill">Auto-aanvullen</string>
<string name="autofill_service_name">KeePassDX auto-aanvullendienst</string> <string name="autofill_service_name">KeePassDX auto-aanvullendienst</string>
<string name="autofill_sign_in_prompt">Inloggen met KeePassDX</string> <string name="autofill_sign_in_prompt">Inloggen met KeePassDX</string>
<string name="set_autofill_service_title">Dienst voor automatisch aanvullen</string> <string name="set_autofill_service_title">Dienst automatisch aanvullen</string>
<string name="autofill_explanation_summary">Schakel de dienst in om formulieren in andere apps in te vullen</string> <string name="autofill_explanation_summary">Schakel de dienst in om formulieren in andere apps in te vullen</string>
<string name="password_size_title">Gegenereerde wachtwoordlengte</string> <string name="password_size_title">Gegenereerde wachtwoordlengte</string>
<string name="password_size_summary">Standaardlengte van gegenereerd wachtwoord instellen</string> <string name="password_size_summary">Stel de standaardlengte van gegenereerd wachtwoord in</string>
<string name="list_password_generator_options_title">Wachtwoordtekens</string> <string name="list_password_generator_options_title">Wachtwoordtekens</string>
<string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string> <string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string>
<string name="clipboard">Klembord</string> <string name="clipboard">Klembord</string>
<string name="clipboard_notifications_title">Klembordmeldingen</string> <string name="clipboard_notifications_title">Klembordmeldingen</string>
<string name="clipboard_notifications_summary">Toon klembordmeldingen om velden te kopiëren bij het bekijken van een item</string> <string name="clipboard_notifications_summary">Schakel klembordmeldingen in om velden te kopiëren bij het bekijken van een item</string>
<string name="clipboard_warning">Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.</string> <string name="clipboard_warning">Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.</string>
<string name="lock">Vergrendelen</string> <string name="lock">Vergrendelen</string>
<string name="lock_database_screen_off_title">Schermvergrendeling</string> <string name="lock_database_screen_off_title">Schermvergrendeling</string>
@@ -222,9 +222,9 @@
<string name="biometric_unlock_enable_title">Ontgrendelen met biometrie</string> <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_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_title">Coderingssleutels verwijderen</string>
<string name="biometric_delete_all_key_summary">Alle sleutels voor biometrische herkenning verwijderen</string> <string name="biometric_delete_all_key_summary">Alle coderingssleutels met betrekking tot geavanceerde ontgrendelingsherkenning verwijderen</string>
<string name="unavailable_feature_text">Kan deze functie niet starten.</string> <string name="unavailable_feature_text">Kan deze functie niet starten.</string>
<string name="unavailable_feature_version">Het apparaat draait op Android %1$s, maar %2$s of hoger is vereist.</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> <string name="unavailable_feature_hardware">De bijbehorende hardware werd niet gevonden.</string>
<string name="file_name">Bestandsnaam</string> <string name="file_name">Bestandsnaam</string>
<string name="path">Pad</string> <string name="path">Pad</string>
@@ -241,19 +241,19 @@
<string name="database_description_title">Databaseomschrijving</string> <string name="database_description_title">Databaseomschrijving</string>
<string name="database_version_title">Databaseversie</string> <string name="database_version_title">Databaseversie</string>
<string name="text_appearance">Tekst</string> <string name="text_appearance">Tekst</string>
<string name="application_appearance">App</string> <string name="application_appearance">Gebruikersomgeving</string>
<string name="other">Overig</string> <string name="other">Overig</string>
<string name="keyboard">Toetsenbord</string> <string name="keyboard">Toetsenbord</string>
<string name="magic_keyboard_title">Magikeyboard</string> <string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren</string> <string name="magic_keyboard_explanation_summary">Activeer een aangepast toetsenbord dat je wachtwoorden en identiteitsvelden vult</string>
<string name="allow_no_password_title">Geen hoofdwachtwoord toestaan</string> <string name="allow_no_password_title">Geen hoofdwachtwoord toestaan</string>
<string name="allow_no_password_summary">Maakt het mogelijk op de knop \"Openen\" te tikken als er geen inloggegevens zijn geselecteerd</string> <string name="allow_no_password_summary">Schakel de knop \"Openen\" in als er geen referenties zijn geselecteerd</string>
<string name="enable_read_only_title">Alleen-lezen</string> <string name="enable_read_only_title">Alleen-lezen</string>
<string name="enable_read_only_summary">Open de database standaard alleen-lezen</string> <string name="enable_read_only_summary">Open de database standaard alleen-lezen</string>
<string name="enable_education_screens_title">Informatieve tips</string> <string name="enable_education_screens_title">Informatieve tips</string>
<string name="enable_education_screens_summary">Markeer elementen om te leren hoe de app werkt</string> <string name="enable_education_screens_summary">Markeer elementen om te leren hoe de app werkt</string>
<string name="reset_education_screens_title">Informatieve tips opnieuw instellen</string> <string name="reset_education_screens_title">Informatieve tips opnieuw instellen</string>
<string name="reset_education_screens_summary">Informatieve tips opnieuw weergeven</string> <string name="reset_education_screens_summary">Informatieve tips opnieuw tonen</string>
<string name="reset_education_screens_text">Informatieve tips opnieuw ingesteld</string> <string name="reset_education_screens_text">Informatieve tips opnieuw ingesteld</string>
<string name="education_create_database_title">Maak je databasebestand aan</string> <string name="education_create_database_title">Maak je databasebestand aan</string>
<string name="education_create_database_summary">Maak je eerste wachtwoordbeheerbestand aan.</string> <string name="education_create_database_summary">Maak je eerste wachtwoordbeheerbestand aan.</string>
@@ -276,7 +276,7 @@
<string name="education_read_only_summary">Wijzig de opstartmodus van de sessie. <string name="education_read_only_summary">Wijzig de opstartmodus van de sessie.
\n \n
\nIn alleen-lezenmodus kunnen geen onbedoelde wijzigingen worden gemaakt. \nIn alleen-lezenmodus kunnen geen onbedoelde wijzigingen worden gemaakt.
\nIn schrijfmodus kun je elementen toevoegen, verwijderen of aanpassen.</string> \nIn schrijfmodus kun je naar wens elementen toevoegen, verwijderen of aanpassen.</string>
<string name="education_field_copy_title">Veld kopiëren toestaan</string> <string name="education_field_copy_title">Veld kopiëren toestaan</string>
<string name="education_field_copy_summary">Kopieer een veld en plak de inhoud waar je maar wilt. <string name="education_field_copy_summary">Kopieer een veld en plak de inhoud waar je maar wilt.
\n \n
@@ -297,16 +297,15 @@
<string name="html_text_dev_feature_encourage">motiveer je ontwikkelaars om &lt;strong&gt;nieuwe functies&lt;/strong&gt; te creëren en &lt;strong&gt;problemen op te lossen&lt;/strong&gt;.</string> <string name="html_text_dev_feature_encourage">motiveer je ontwikkelaars om &lt;strong&gt;nieuwe functies&lt;/strong&gt; te creëren en &lt;strong&gt;problemen op te lossen&lt;/strong&gt;.</string>
<string name="html_text_dev_feature_thanks">Hartelijk bedankt voor je bijdrage.</string> <string name="html_text_dev_feature_thanks">Hartelijk bedankt voor je bijdrage.</string>
<string name="html_text_dev_feature_work_hard">We zijn druk bezig om deze functie snel beschikbaar te stellen.</string> <string name="html_text_dev_feature_work_hard">We zijn druk bezig om deze functie snel beschikbaar te stellen.</string>
<string name="html_text_dev_feature_upgrade">Vergeet niet om je app up-to-date te houden door nieuwe versies te installeren.</string> <string name="html_text_dev_feature_upgrade">Vergeet niet je app up-to-date te houden door nieuwe versies te installeren.</string>
<string name="download">Downloaden</string> <string name="download">Downloaden</string>
<string name="contribute">Bijdragen</string> <string name="contribute">Bijdragen</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">App-thema</string> <string name="style_choose_title">App-thema</string>
<string name="style_choose_summary">Thema gebruikt in de app</string> <string name="style_choose_summary">Thema gebruikt in de app</string>
<string name="icon_pack_choose_title">Pictogrammenverzameling</string> <string name="icon_pack_choose_title">Icon pack</string>
<string name="icon_pack_choose_summary">Pictogrammenverzameling in gebruik</string> <string name="icon_pack_choose_summary">Gebruikt Icon Pack</string>
<string name="build_label">Build %1$s</string> <string name="build_label">Build %1$s</string>
<string name="keyboard_name">Magikeyboard</string> <string name="keyboard_name">Magikeyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string> <string name="keyboard_label">Magikeyboard (KeePassDX)</string>
@@ -329,14 +328,14 @@
<string name="selection_mode">Selectiemodus</string> <string name="selection_mode">Selectiemodus</string>
<string name="do_not_kill_app">App niet afsluiten…</string> <string name="do_not_kill_app">App niet afsluiten…</string>
<string name="lock_database_back_root_title">Druk \'Terug\' om te vergrendelen</string> <string name="lock_database_back_root_title">Druk \'Terug\' om te vergrendelen</string>
<string name="lock_database_back_root_summary">Vergrendel de database wanneer de gebruiker op de knop Terug in het hoofdscherm klikt</string> <string name="lock_database_back_root_summary">Vergrendel de database wanneer de gebruiker in het hoofdscherm op de knop Terug klikt</string>
<string name="clear_clipboard_notification_title">Wissen bij afsluiten</string> <string name="clear_clipboard_notification_title">Wissen bij afsluiten</string>
<string name="clear_clipboard_notification_summary">Vergrendel de database wanneer de duur van het klembord verloopt of de melding wordt gesloten nadat u deze bent gaan gebruiken</string> <string name="clear_clipboard_notification_summary">Vergrendel de database wanneer de duur van het klembord verloopt of de melding wordt gesloten nadat u deze bent gaan gebruiken</string>
<string name="recycle_bin">Prullenmand</string> <string name="recycle_bin">Prullenmand</string>
<string name="keyboard_selection_entry_title">Itemselectie</string> <string name="keyboard_selection_entry_title">Itemselectie</string>
<string name="keyboard_selection_entry_summary">Invoervelden in Magikeyboard tonen bij het bekijken van een item</string> <string name="keyboard_selection_entry_summary">Invoervelden in Magikeyboard tonen bij het bekijken van een item</string>
<string name="delete_entered_password_title">Wachtwoord wissen</string> <string name="delete_entered_password_title">Wachtwoord wissen</string>
<string name="delete_entered_password_summary">Wis het ingevoerde wachtwoord na een poging met een database te verbinden</string> <string name="delete_entered_password_summary">Wis het ingevoerde wachtwoord na een verbindingspoging met een database</string>
<string name="content_description_open_file">Bestand openen</string> <string name="content_description_open_file">Bestand openen</string>
<string name="content_description_node_children">Onderliggende items</string> <string name="content_description_node_children">Onderliggende items</string>
<string name="content_description_add_node">Knooppunt toevoegen</string> <string name="content_description_add_node">Knooppunt toevoegen</string>
@@ -354,7 +353,7 @@
<string name="entry_UUID">UUID</string> <string name="entry_UUID">UUID</string>
<string name="error_move_entry_here">Je kan hier geen item plaatsen.</string> <string name="error_move_entry_here">Je kan hier geen item plaatsen.</string>
<string name="error_copy_entry_here">Je kan hier geen item kopiëren.</string> <string name="error_copy_entry_here">Je kan hier geen item kopiëren.</string>
<string name="list_groups_show_number_entries_title">Toon het aantal items</string> <string name="list_groups_show_number_entries_title">Aantal items tonen</string>
<string name="list_groups_show_number_entries_summary">Toon het aantal items in een groep</string> <string name="list_groups_show_number_entries_summary">Toon het aantal items in een groep</string>
<string name="content_description_background">Achtergrond</string> <string name="content_description_background">Achtergrond</string>
<string name="content_description_update_from_list">Update</string> <string name="content_description_update_from_list">Update</string>
@@ -362,8 +361,8 @@
<string name="error_create_database_file">Kan geen database aanmaken met dit wachtwoord en sleutelbestand.</string> <string name="error_create_database_file">Kan geen database aanmaken met dit wachtwoord en sleutelbestand.</string>
<string name="menu_advanced_unlock_settings">Geavanceerd ontgrendelen</string> <string name="menu_advanced_unlock_settings">Geavanceerd ontgrendelen</string>
<string name="biometric">Biometrie</string> <string name="biometric">Biometrie</string>
<string name="biometric_auto_open_prompt_title">Automatisch om biometrie vragen</string> <string name="biometric_auto_open_prompt_title">Auto-open suggestie</string>
<string name="biometric_auto_open_prompt_summary">Automatisch om biometrie vragen als een database hiervoor is ingesteld</string> <string name="biometric_auto_open_prompt_summary">Automatisch om geavanceerde ontgrendeling vragen als een database hiervoor is ingesteld</string>
<string name="enable">Inschakelen</string> <string name="enable">Inschakelen</string>
<string name="disable">Uitschakelen</string> <string name="disable">Uitschakelen</string>
<string name="master_key">Hoofdsleutel</string> <string name="master_key">Hoofdsleutel</string>
@@ -391,7 +390,7 @@
<string name="contains_duplicate_uuid">De database bevat dubbele UUID\'s.</string> <string name="contains_duplicate_uuid">De database bevat dubbele UUID\'s.</string>
<string name="contains_duplicate_uuid_procedure">Probleem oplossen door nieuwe UUID\'s te genereren voor de duplicaten\?</string> <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="database_opened">Database geopend</string>
<string name="clipboard_explanation_summary">Kopieer invoervelden met behulp van het klembord van uw apparaat</string> <string name="clipboard_explanation_summary">Kopieer invoervelden met behulp van het klembord van dit apparaat</string>
<string name="advanced_unlock_explanation_summary">Geavanceerde ontgrendeling gebruiken om een database gemakkelijker te openen</string> <string name="advanced_unlock_explanation_summary">Geavanceerde ontgrendeling gebruiken om een database gemakkelijker te openen</string>
<string name="database_data_compression_title">Gegevenscompressie</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="database_data_compression_summary">Gegevenscompressie verkleint de omvang van de database</string>
@@ -414,20 +413,20 @@
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string> <string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
<string name="education_setup_OTP_title">OTP instellen</string> <string name="education_setup_OTP_title">OTP instellen</string>
<string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string> <string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string>
<string name="remember_database_locations_title">Databaselocaties onthouden</string> <string name="remember_database_locations_title">Databaselocaties opslaan</string>
<string name="hide_expired_entries_summary">Verlopen items worden niet getoond</string> <string name="hide_expired_entries_summary">Verlopen items worden niet getoond</string>
<string name="hide_expired_entries_title">Verberg verlopen items</string> <string name="hide_expired_entries_title">Verlopen items verbergen</string>
<string name="download_complete">Klaar!</string> <string name="download_complete">Voltooid!</string>
<string name="download_finalization">Voltooien…</string> <string name="download_finalization">Voltooien…</string>
<string name="download_progression">Voortgang: %1$d%%</string> <string name="download_progression">Voortgang: %1$d%%</string>
<string name="download_initialization">Initialiseren…</string> <string name="download_initialization">Initialiseren…</string>
<string name="download_attachment">Download %1$s</string> <string name="download_attachment">Download %1$s</string>
<string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat wordt gevraagd voor tweefactorauthenticatie (2FA).</string> <string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren voor tweefactorauthenticatie (2FA).</string>
<string name="enable_auto_save_database_title">Automatisch opslaan</string> <string name="enable_auto_save_database_title">Automatisch opslaan</string>
<string name="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string> <string name="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string>
<string name="autofill_auto_search_title">Automatisch zoeken</string> <string name="autofill_auto_search_title">Automatisch zoeken</string>
<string name="recycle_bin_group_title">Prullenbak</string> <string name="recycle_bin_group_title">Prullenbak</string>
<string name="lock_database_show_button_summary">Geeft de vergrendelknop weer in de gebruikersinterface</string> <string name="lock_database_show_button_summary">Geef de vergrendelknop weer in de gebruikersinterface</string>
<string name="lock_database_show_button_title">Vergrendelknop weergeven</string> <string name="lock_database_show_button_title">Vergrendelknop weergeven</string>
<string name="autofill_preference_title">Instellingen voor automatisch aanvullen</string> <string name="autofill_preference_title">Instellingen voor automatisch aanvullen</string>
<string name="keystore_not_accessible">De sleutelopslag is niet correct geïnitialiseerd.</string> <string name="keystore_not_accessible">De sleutelopslag is niet correct geïnitialiseerd.</string>
@@ -435,10 +434,10 @@
<string name="warning_database_link_revoked">Toegang tot het bestand ingetrokken door bestandsbeheer</string> <string name="warning_database_link_revoked">Toegang tot het bestand ingetrokken door bestandsbeheer</string>
<string name="warning_database_read_only">Bestandstoegang verlenen om databasewijzigingen op te slaan</string> <string name="warning_database_read_only">Bestandstoegang verlenen om databasewijzigingen op te slaan</string>
<string name="command_execution">Opdracht uitvoeren…</string> <string name="command_execution">Opdracht uitvoeren…</string>
<string name="hide_broken_locations_summary">Verberg gebroken links in de lijst met recente databases</string> <string name="hide_broken_locations_summary">Gebroken links in de lijst met recente databases verbergen</string>
<string name="hide_broken_locations_title">Verberg corrupte databasekoppelingen</string> <string name="hide_broken_locations_title">Corrupte databasekoppelingen verbergen</string>
<string name="remember_keyfile_locations_summary">Onthoudt waar de databasesleutelbestanden zijn opgeslagen</string> <string name="remember_keyfile_locations_summary">Onthoud de locatie van databasesleutelbestanden</string>
<string name="remember_database_locations_summary">Onthoudt waar de databases zijn opgeslagen</string> <string name="remember_database_locations_summary">Onthoud de locatie van databases</string>
<string name="auto_focus_search_summary">Zoekopdracht aanmaken bij het openen van een database</string> <string name="auto_focus_search_summary">Zoekopdracht aanmaken bij het openen van een database</string>
<string name="auto_focus_search_title">Snel zoeken</string> <string name="auto_focus_search_title">Snel zoeken</string>
<string name="menu_delete_entry_history">Geschiedenis wissen</string> <string name="menu_delete_entry_history">Geschiedenis wissen</string>
@@ -471,19 +470,19 @@
<string name="content_description_add_item">Item toevoegen</string> <string name="content_description_add_item">Item toevoegen</string>
<string name="keyboard_auto_go_action_summary">\"Gaan\"-toetsactie na het indrukken van een \"Veld\"-toets</string> <string name="keyboard_auto_go_action_summary">\"Gaan\"-toetsactie na het indrukken van een \"Veld\"-toets</string>
<string name="keyboard_auto_go_action_title">Automatische toetsactie</string> <string name="keyboard_auto_go_action_title">Automatische toetsactie</string>
<string name="keyboard_previous_fill_in_summary">Schakel automatisch terug naar het vorige toetsenbord na het uitvoeren van de Automatische toetsactie</string> <string name="keyboard_previous_fill_in_summary">Schakel automatisch terug naar het vorige toetsenbord na het uitvoeren van de \"Automatische toetsactie\"</string>
<string name="keyboard_previous_fill_in_title">Automatische toetsactie</string> <string name="keyboard_previous_fill_in_title">Automatische toetsactie</string>
<string name="keyboard_previous_database_credentials_summary">Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm</string> <string name="keyboard_previous_database_credentials_summary">Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm</string>
<string name="keyboard_previous_database_credentials_title">Scherm Databasereferenties</string> <string name="keyboard_previous_database_credentials_title">Scherm Databasereferenties</string>
<string name="keyboard_change">Van toetsenbord wisselen</string> <string name="keyboard_change">Van toetsenbord wisselen</string>
<string name="upload_attachment">%1$s uploaden</string> <string name="upload_attachment">Upload %1$s</string>
<string name="education_add_attachment_summary">Upload een bijlage bij dit item om belangrijke externe gegevens op te slaan.</string> <string name="education_add_attachment_summary">Voeg een bijlage toe aan dit item om belangrijke externe gegevens op te slaan.</string>
<string name="education_add_attachment_title">Bijlage toevoegen</string> <string name="education_add_attachment_title">Bijlage toevoegen</string>
<string name="warning_sure_add_file">Toch het bestand toevoegen\?</string> <string name="warning_sure_add_file">Het bestand toch toevoegen\?</string>
<string name="warning_file_too_big">Een KeePass-database mag alleen kleine hulpprogramma-bestanden bevatten (zoals PGP-sleutelbestanden). <string name="warning_file_too_big">Een KeePass database is bedoeld om alleen kleine gebruiksbestanden te bevatten (zoals PGP sleutelbestanden).
\n \n
\nDe database kan erg groot worden en de prestaties kunnen verminderen bij deze upload.</string> \nMet deze upload kan de database erg groot worden en kunnen de prestaties verminderen.</string>
<string name="warning_replace_file">Als je dit bestand uploadt, wordt het bestaande vervangen.</string> <string name="warning_replace_file">Uploaden van dit bestand zal het bestaande bestand vervangen.</string>
<string name="content_description_credentials_information">Inloggegevens</string> <string name="content_description_credentials_information">Inloggegevens</string>
<string name="warning_remove_unlinked_attachment">Het verwijderen van niet-gekoppelde gegevens kan de omvang van uw database verkleinen, maar kan ook gegevens verwijderen die voor KeePass-plug-ins worden gebruikt.</string> <string name="warning_remove_unlinked_attachment">Het verwijderen van niet-gekoppelde gegevens kan de omvang van uw database verkleinen, maar kan ook gegevens verwijderen die voor KeePass-plug-ins worden gebruikt.</string>
<string name="warning_sure_remove_data">Deze gegevens toch verwijderen\?</string> <string name="warning_sure_remove_data">Deze gegevens toch verwijderen\?</string>
@@ -506,7 +505,6 @@
<string name="keyboard_save_search_info_summary">Probeer gedeelde informatie op te slaan bij een handmatige invoerselectie</string> <string name="keyboard_save_search_info_summary">Probeer gedeelde informatie op te slaan bij een handmatige invoerselectie</string>
<string name="keyboard_save_search_info_title">Gedeelde info opslaan</string> <string name="keyboard_save_search_info_title">Gedeelde info opslaan</string>
<string name="notification">Melding</string> <string name="notification">Melding</string>
<string name="crypto_object_not_initialized">Kan crypto-object niet ophalen.</string>
<string name="biometric_security_update_required">Biometrische beveiligingsupdate vereist.</string> <string name="biometric_security_update_required">Biometrische beveiligingsupdate vereist.</string>
<string name="configure_biometric">Geen biometrische gegevens of apparaatgegevens geregistreerd.</string> <string name="configure_biometric">Geen biometrische gegevens of apparaatgegevens geregistreerd.</string>
<string name="warning_empty_recycle_bin">Alles definitief uit de prullenbak verwijderen\?</string> <string name="warning_empty_recycle_bin">Alles definitief uit de prullenbak verwijderen\?</string>
@@ -514,4 +512,35 @@
<string name="save_mode">Veilige modus</string> <string name="save_mode">Veilige modus</string>
<string name="search_mode">Zoekmodus</string> <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="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">Geavanceerde database-ontgrendeling</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 geavanceerde ontgrendelingsherkenning verwijderen\?</string>
<string name="advanced_unlock_timeout">Time-out voor geavanceerd ontgrendelen</string>
<string name="temp_advanced_unlock_timeout_summary">Duur van geavanceerd ontgrendelingsgebruik voordat de inhoud wordt verwijderd</string>
<string name="temp_advanced_unlock_timeout_title">Vervaltijd voor geavanceerde ontgrendeling</string>
<string name="temp_advanced_unlock_enable_summary">Sla geen versleutelde inhoud op om geavanceerde ontgrendeling te gebruiken</string>
<string name="temp_advanced_unlock_enable_title">Tijdelijke geavanceerde ontgrendeling</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 geavanceerde ontgrendelingstoetsen 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 de knop \"Geavanceerd ontgrendelen\".</string>
<string name="advanced_unlock_prompt_not_initialized">Kan geavanceerde ontgrendelingsprompt niet initialiseren.</string>
<string name="advanced_unlock_scanning_error">Geavanceerde ontgrendelingsfout: %1$s</string>
<string name="advanced_unlock_not_recognized">Kan geavanceerde ontgrendelingsafdruk niet herkennen</string>
<string name="advanced_unlock_invalid_key">Kan de geavanceerde ontgrendelingssleutel niet lezen. Verwijder deze en herhaal de herkenningsprocedure voor het ontgrendelen.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Databasegegevens uitpakken met geavanceerde ontgrendelingsgegevens</string>
<string name="advanced_unlock_prompt_extract_credential_title">Open database met geavanceerde ontgrendelingsherkenning</string>
<string name="advanced_unlock_prompt_store_credential_message">Waarschuwing: je moet nog steeds je hoofdwachtwoord onthouden als je geavanceerde ontgrendelingsherkenning gebruikt.</string>
<string name="advanced_unlock_prompt_store_credential_title">Geavanceerde ontgrendelingsherkenning</string>
<string name="open_advanced_unlock_prompt_store_credential">Open de geavanceerde ontgrendelingsprompt om inloggegevens op te slaan</string>
<string name="open_advanced_unlock_prompt_unlock_database">Open de geavanceerde ontgrendelingsprompt om de database te ontgrendelen</string>
<string name="menu_keystore_remove_key">Geavanceerde ontgrendelingssleutel verwijderen</string>
<string name="error_field_name_already_exists">De veldnaam bestaat al.</string>
</resources> </resources>

View File

@@ -228,7 +228,7 @@
<string name="create_keepass_file">Utwórz nową bazę danych</string> <string name="create_keepass_file">Utwórz nową bazę danych</string>
<string name="recycle_bin_title">Wykorzystaj kosz</string> <string name="recycle_bin_title">Wykorzystaj kosz</string>
<string name="recycle_bin_summary">Przenosi grupy i wpisy do grupy \"Kosz\" przed usunięciem</string> <string name="recycle_bin_summary">Przenosi grupy i wpisy do grupy \"Kosz\" przed usunięciem</string>
<string name="monospace_font_fields_enable_title">Krój pisma pola</string> <string name="monospace_font_fields_enable_title">Czcionka pola</string>
<string name="monospace_font_fields_enable_summary">Zmień czcionkę użytą w polach, aby poprawić widoczność postaci</string> <string name="monospace_font_fields_enable_summary">Zmień czcionkę użytą w polach, aby poprawić widoczność postaci</string>
<string name="allow_copy_password_title">Zaufanie do schowka</string> <string name="allow_copy_password_title">Zaufanie do schowka</string>
<string name="allow_copy_password_summary">Zezwalanie na kopiowanie hasła wejściowego i chronionych pól do schowka</string> <string name="allow_copy_password_summary">Zezwalanie na kopiowanie hasła wejściowego i chronionych pól do schowka</string>
@@ -298,7 +298,6 @@
<string name="contribute">Przyczyń się</string> <string name="contribute">Przyczyń się</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Motyw aplikacji</string> <string name="style_choose_title">Motyw aplikacji</string>
<string name="style_choose_summary">Motyw używany w aplikacji</string> <string name="style_choose_summary">Motyw używany w aplikacji</string>
<string name="icon_pack_choose_title">Pakiet ikon</string> <string name="icon_pack_choose_title">Pakiet ikon</string>
@@ -504,7 +503,6 @@
<string name="keyboard_save_search_info_summary">Spróbuj zapisać udostępnione informacje podczas ręcznego wybierania pozycji</string> <string name="keyboard_save_search_info_summary">Spróbuj zapisać udostępnione informacje podczas ręcznego wybierania pozycji</string>
<string name="keyboard_save_search_info_title">Zapisz udostępnione informacje</string> <string name="keyboard_save_search_info_title">Zapisz udostępnione informacje</string>
<string name="notification">Powiadomienia</string> <string name="notification">Powiadomienia</string>
<string name="crypto_object_not_initialized">Nie można pobrać obiektu kryptograficznego.</string>
<string name="biometric_security_update_required">Wymagana aktualizacja zabezpieczeń biometrycznych.</string> <string name="biometric_security_update_required">Wymagana aktualizacja zabezpieczeń biometrycznych.</string>
<string name="configure_biometric">Nie zarejestrowano żadnych danych biometrycznych ani danych urządzenia.</string> <string name="configure_biometric">Nie zarejestrowano żadnych danych biometrycznych ani danych urządzenia.</string>
<string name="warning_empty_recycle_bin">Trwale usunąć wszystkie węzły z kosza\?</string> <string name="warning_empty_recycle_bin">Trwale usunąć wszystkie węzły z kosza\?</string>
@@ -529,4 +527,29 @@
<string name="advanced_unlock_prompt_store_credential_message">Ostrzeżenie: Jeśli używasz zaawansowanego rozpoznawania odblokowania, nadal musisz zapamiętać hasło główne.</string> <string name="advanced_unlock_prompt_store_credential_message">Ostrzeżenie: Jeśli używasz zaawansowanego rozpoznawania odblokowania, nadal musisz zapamiętać hasło główne.</string>
<string name="advanced_unlock_prompt_store_credential_title">Zaawansowane rozpoznawanie odblokowania</string> <string name="advanced_unlock_prompt_store_credential_title">Zaawansowane rozpoznawanie odblokowania</string>
<string name="menu_keystore_remove_key">Usuń zaawansowany klucz odblokowujący</string> <string name="menu_keystore_remove_key">Usuń zaawansowany klucz odblokowujący</string>
<string name="education_advanced_unlock_summary">Połącz swoje hasło ze zeskanowanymi danymi biometrycznymi lub danymi logowania urządzenia, aby szybko odblokować bazę danych.</string>
<string name="education_advanced_unlock_title">Zaawansowane odblokowywanie bazy danych</string>
<string name="advanced_unlock_timeout">Limit czasu zaawansowanego odblokowywania</string>
<string name="temp_advanced_unlock_timeout_summary">Czas trwania zaawansowanego odblokowywania przed usunięciem jego zawartości</string>
<string name="temp_advanced_unlock_timeout_title">Wygaśnięcie zaawansowanego odblokowania</string>
<string name="temp_advanced_unlock_enable_summary">Nie przechowuj żadnych zaszyfrowanych treści, aby korzystać z zaawansowanego odblokowywania</string>
<string name="advanced_unlock_tap_delete">Naciśnij, aby usunąć zaawansowane klucze odblokowujące</string>
<string name="content">Zawartość</string>
<string name="advanced_unlock_prompt_extract_credential_title">Otwórz bazę danych z zaawansowanym rozpoznawaniem odblokowania</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="advanced_unlock_scanning_error">Błąd zaawansowanego odblokowywania: %1$s</string>
<string name="error_rebuild_list">Nie można poprawnie odbudować listy.</string>
<string name="error_database_uri_null">Nie można pobrać identyfikatora URI bazy danych.</string>
<string name="autofill_inline_suggestions_keyboard">Dodano sugestie autouzupełniania.</string>
<string name="autofill_inline_suggestions_title">Sugestie wbudowane</string>
<string name="autofill_inline_suggestions_summary">Spróbuj wyświetlić sugestie autouzupełniania bezpośrednio z kompatybilnej klawiatury</string>
<string name="temp_advanced_unlock_enable_title">Zaawansowane odblokowywanie tymczasowe</string>
<string name="advanced_unlock_prompt_not_initialized">Nie można zainicjować zaawansowanego monitu o odblokowanie.</string>
<string name="open_advanced_unlock_prompt_store_credential">Otwórz zaawansowany monit o odblokowanie, aby zapisać poświadczenia</string>
<string name="open_advanced_unlock_prompt_unlock_database">Otwórz zaawansowany monit o odblokowanie, aby odblokować bazę danych</string>
<string name="warning_database_revoked">Dostęp do pliku odwołany przez menedżera plików, zamknij bazę danych i otwórz ją ponownie z jej lokalizacji.</string>
<string name="warning_database_info_changed_options">Nadpisz zewnętrzne modyfikacje, zapisując bazę danych lub przeładuj ją z najnowszymi zmianami.</string>
<string name="warning_database_info_changed">Informacje zawarte w pliku bazy danych zostały zmodyfikowane poza aplikacją.</string>
<string name="menu_reload_database">Załaduj ponownie bazę danych</string>
</resources> </resources>

View File

@@ -294,7 +294,6 @@
<string name="contribute">Contribuir</string> <string name="contribute">Contribuir</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Tema do aplicativo</string> <string name="style_choose_title">Tema do aplicativo</string>
<string name="style_choose_summary">Tema usado no aplicativo</string> <string name="style_choose_summary">Tema usado no aplicativo</string>
<string name="icon_pack_choose_title">Pacote de ícones</string> <string name="icon_pack_choose_title">Pacote de ícones</string>

View File

@@ -281,7 +281,6 @@
<string name="contribute">Contribuir</string> <string name="contribute">Contribuir</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Tema da app</string> <string name="style_choose_title">Tema da app</string>
<string name="style_choose_summary">Tema usado na app</string> <string name="style_choose_summary">Tema usado na app</string>
<string name="icon_pack_choose_title">Pacote de ícones</string> <string name="icon_pack_choose_title">Pacote de ícones</string>

View File

@@ -20,7 +20,6 @@
<string name="icon_pack_choose_title">Pacote de ícones</string> <string name="icon_pack_choose_title">Pacote de ícones</string>
<string name="style_choose_summary">Tema usado na app</string> <string name="style_choose_summary">Tema usado na app</string>
<string name="style_choose_title">Tema da app</string> <string name="style_choose_title">Tema da app</string>
<string name="kdf_Argon2">Argon2</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>

View File

@@ -406,7 +406,6 @@
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Tema aplicației</string> <string name="style_choose_title">Tema aplicației</string>
<string name="style_choose_summary">Tema folosită în aplicație</string> <string name="style_choose_summary">Tema folosită în aplicație</string>
<string name="icon_pack_choose_title">Pachet de pictograme</string> <string name="icon_pack_choose_title">Pachet de pictograme</string>

View File

@@ -215,12 +215,12 @@
<string name="lock_database_screen_off_title">Блокировка экрана</string> <string name="lock_database_screen_off_title">Блокировка экрана</string>
<string name="lock_database_screen_off_summary">Блокировать базу при отключении экрана</string> <string name="lock_database_screen_off_summary">Блокировать базу при отключении экрана</string>
<string name="advanced_unlock">Расширенная разблокировка</string> <string name="advanced_unlock">Расширенная разблокировка</string>
<string name="biometric_unlock_enable_title">Сканирование биометрического ключа</string> <string name="biometric_unlock_enable_title">Биометрическая разблокировка</string>
<string name="biometric_unlock_enable_summary">Включить разблокировку базы при помощи биометрического ключа</string> <string name="biometric_unlock_enable_summary">Включить разблокировку базы при помощи биометрического ключа</string>
<string name="biometric_delete_all_key_title">Удалить ключи шифрования</string> <string name="biometric_delete_all_key_title">Удалить ключи шифрования</string>
<string name="biometric_delete_all_key_summary">Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки</string> <string name="biometric_delete_all_key_summary">Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки</string>
<string name="unavailable_feature_text">Невозможно запустить эту функцию.</string> <string name="unavailable_feature_text">Невозможно использовать эту функцию.</string>
<string name="unavailable_feature_version">Ваша версия Android %1$s, но требуется %2$s.</string> <string name="unavailable_feature_version">Ваша версия Android %1$s, требуется %2$s.</string>
<string name="unavailable_feature_hardware">Соответствующее оборудование не найдено.</string> <string name="unavailable_feature_hardware">Соответствующее оборудование не найдено.</string>
<string name="file_name">Имя файла</string> <string name="file_name">Имя файла</string>
<string name="path">Путь</string> <string name="path">Путь</string>
@@ -228,8 +228,8 @@
<string name="create_keepass_file">Создать новую базу</string> <string name="create_keepass_file">Создать новую базу</string>
<string name="recycle_bin_title">Использовать \"корзину\"</string> <string name="recycle_bin_title">Использовать \"корзину\"</string>
<string name="recycle_bin_summary">Перемещать группу или запись в \"корзину\" вместо удаления</string> <string name="recycle_bin_summary">Перемещать группу или запись в \"корзину\" вместо удаления</string>
<string name="monospace_font_fields_enable_title">Шрифт полей</string> <string name="monospace_font_fields_enable_title">Особый шрифт полей</string>
<string name="monospace_font_fields_enable_summary">Использовать в полях особый шрифт для лучшей читаемости</string> <string name="monospace_font_fields_enable_summary">Использовать в полях специальный шрифт для лучшей читаемости</string>
<string name="allow_copy_password_title">Доверять буферу обмена</string> <string name="allow_copy_password_title">Доверять буферу обмена</string>
<string name="allow_copy_password_summary">Разрешить копирование пароля и защищённых полей в буфер обмена</string> <string name="allow_copy_password_summary">Разрешить копирование пароля и защищённых полей в буфер обмена</string>
<string name="allow_copy_password_warning">Внимание: буфер обмена доступен всем приложениям. Если копируются чувствительные данные, другие программы могут их перехватить.</string> <string name="allow_copy_password_warning">Внимание: буфер обмена доступен всем приложениям. Если копируются чувствительные данные, другие программы могут их перехватить.</string>
@@ -298,7 +298,6 @@
<string name="contribute">Содействие</string> <string name="contribute">Содействие</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="kdf_Argon2">Argon2</string>
<string name="style_choose_title">Тема приложения</string> <string name="style_choose_title">Тема приложения</string>
<string name="style_choose_summary">Тема, используемая в приложении</string> <string name="style_choose_summary">Тема, используемая в приложении</string>
<string name="icon_pack_choose_title">Набор значков</string> <string name="icon_pack_choose_title">Набор значков</string>
@@ -495,7 +494,6 @@
<string name="autofill_save_search_info_title">Сохранять данные поиска</string> <string name="autofill_save_search_info_title">Сохранять данные поиска</string>
<string name="keyboard_save_search_info_summary">Сохранять общую информацию при ручном выборе записи</string> <string name="keyboard_save_search_info_summary">Сохранять общую информацию при ручном выборе записи</string>
<string name="keyboard_save_search_info_title">Сохранять общие данные</string> <string name="keyboard_save_search_info_title">Сохранять общие данные</string>
<string name="crypto_object_not_initialized">Невозможно получить доступ к зашифрованному объекту.</string>
<string name="keyboard_previous_lock_title">Блокировка базы</string> <string name="keyboard_previous_lock_title">Блокировка базы</string>
<string name="keyboard_previous_lock_summary">Автоматически переключаться на предыдущую клавиатуру после блокировки базы</string> <string name="keyboard_previous_lock_summary">Автоматически переключаться на предыдущую клавиатуру после блокировки базы</string>
<string name="show_uuid_summary">Показывать UUID, связанный с записью</string> <string name="show_uuid_summary">Показывать UUID, связанный с записью</string>
@@ -514,17 +512,17 @@
<string name="error_registration_read_only">Сохранение новых записей невозможно, т.к. база открыта только для чтения</string> <string name="error_registration_read_only">Сохранение новых записей невозможно, т.к. база открыта только для чтения</string>
<string name="error_field_name_already_exists">Поле с таким именем уже существует.</string> <string name="error_field_name_already_exists">Поле с таким именем уже существует.</string>
<string name="device_credential_unlock_enable_summary">Позволяет использовать учётные данные вашего устройства для открытия базы</string> <string name="device_credential_unlock_enable_summary">Позволяет использовать учётные данные вашего устройства для открытия базы</string>
<string name="device_credential_unlock_enable_title">Разблокировка учётных данных устройства</string> <string name="device_credential_unlock_enable_title">Разблокировка учётными данными устройства</string>
<string name="device_credential">Учётные данные устройства</string> <string name="device_credential">Учётные данные устройства</string>
<string name="credential_before_click_advanced_unlock_button">Введите пароль и нажмите кнопку \"Расширенная разблокировка\".</string> <string name="credential_before_click_advanced_unlock_button">Введите пароль и нажмите кнопку \"Расширенная разблокировка\".</string>
<string name="advanced_unlock_prompt_not_initialized">Невозможно инициализировать запрос расширенной разблокировки.</string> <string name="advanced_unlock_prompt_not_initialized">Невозможно инициализировать запрос расширенной разблокировки.</string>
<string name="advanced_unlock_not_recognized">Невозможно распознать расширенную разблокировку</string> <string name="advanced_unlock_not_recognized">Невозможно распознать расширенную разблокировку</string>
<string name="advanced_unlock_invalid_key">Невозможно прочитать ключ расширенной разблокировки. Удалите его и повторите процедуру распознавания разблокировки.</string> <string name="advanced_unlock_invalid_key">Невозможно прочитать ключ расширенной разблокировки. Удалите его и повторите процедуру распознавания разблокировки.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Извлекать учётные данные базы с использованием расширенной разблокировки</string> <string name="advanced_unlock_prompt_extract_credential_message">Извлекать учётные данные базы с использованием расширенной разблокировки</string>
<string name="advanced_unlock_prompt_extract_credential_title">Открывать базу с расширенным распознаванием разблокировки</string> <string name="advanced_unlock_prompt_extract_credential_title">Открыть базу с расширенным распознаванием разблокировки</string>
<string name="advanced_unlock_prompt_store_credential_message">Предупреждение: даже при использовании расширенной разблокировки вам всё равно необходимо помнить главный пароль.</string> <string name="advanced_unlock_prompt_store_credential_message">Предупреждение: даже при использовании расширенной разблокировки вам всё равно необходимо помнить главный пароль.</string>
<string name="open_advanced_unlock_prompt_store_credential">Открывать запрос расширенной разблокировки для сохранения учётных данных</string> <string name="open_advanced_unlock_prompt_store_credential">Открыть запрос расширенной разблокировки для сохранения учётных данных</string>
<string name="open_advanced_unlock_prompt_unlock_database">Открывать запрос расширенной разблокировки для разблокировки базы</string> <string name="open_advanced_unlock_prompt_unlock_database">Открыть запрос расширенной разблокировки для разблокировки базы</string>
<string name="advanced_unlock_delete_all_key_warning">Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки\?</string> <string name="advanced_unlock_delete_all_key_warning">Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки\?</string>
<string name="advanced_unlock_scanning_error">Ошибка расширенной разблокировки: %1$s</string> <string name="advanced_unlock_scanning_error">Ошибка расширенной разблокировки: %1$s</string>
<string name="advanced_unlock_prompt_store_credential_title">Распознавание расширенной разблокировки</string> <string name="advanced_unlock_prompt_store_credential_title">Распознавание расширенной разблокировки</string>
@@ -535,12 +533,23 @@
<string name="backspace">Backspace</string> <string name="backspace">Backspace</string>
<string name="select_entry">Выберите запись</string> <string name="select_entry">Выберите запись</string>
<string name="education_advanced_unlock_title">Расширенная разблокировка базы</string> <string name="education_advanced_unlock_title">Расширенная разблокировка базы</string>
<string name="advanced_unlock_timeout">Ожидание расширенной разблокировки</string> <string name="advanced_unlock_timeout">Срок действия расширенной разблокировки</string>
<string name="education_advanced_unlock_summary">Свяжите пароль с отсканированными биометрическими данными или учётными данными устройства, чтобы быстро разблокировать базу.</string> <string name="education_advanced_unlock_summary">Свяжите пароль с отсканированными биометрическими данными или учётными данными устройства, чтобы быстро разблокировать базу.</string>
<string name="temp_advanced_unlock_timeout_summary">Продолжительность использования содержимого расширенной разблокировки до его удаления</string> <string name="temp_advanced_unlock_timeout_summary">Продолжительность использования содержимого расширенной разблокировки до его удаления</string>
<string name="temp_advanced_unlock_timeout_title">Срок действия расширенной разблокировки</string> <string name="temp_advanced_unlock_timeout_title">Время действия</string>
<string name="temp_advanced_unlock_enable_title">Временная расширенная разблокировка</string> <string name="temp_advanced_unlock_enable_title">Временная расширенная разблокировка</string>
<string name="temp_advanced_unlock_enable_summary">Не сохранять зашифрованное содержимое для использования расширенной разблокировки</string> <string name="temp_advanced_unlock_enable_summary">Не сохранять зашифрованное содержимое для использования расширенной разблокировки</string>
<string name="advanced_unlock_tap_delete">Нажмите, чтобы удалить ключи расширенной разблокировки</string> <string name="advanced_unlock_tap_delete">Нажмите, чтобы удалить ключи расширенной разблокировки</string>
<string name="content">Содержимое</string> <string name="content">Содержимое</string>
<string name="kdf_Argon2id">Argon2ID</string>
<string name="kdf_Argon2d">Argon2D</string>
<string name="error_database_uri_null">Невозможно получить URI базы.</string>
<string name="error_rebuild_list">Невозможно правильно перестроить список.</string>
<string name="autofill_inline_suggestions_keyboard">Предложения автозаполнения добавлены.</string>
<string name="autofill_inline_suggestions_summary">Показывать предложения автозаполнения непосредственно в совместимой клавиатуре</string>
<string name="autofill_inline_suggestions_title">Встроенные предложения</string>
<string name="warning_database_revoked">Доступ к файлу отозван файловым менеджером, закройте базу и снова откройте.</string>
<string name="warning_database_info_changed_options">Сохранить базу, перезаписав внешние изменения, или перезагрузить её с последними изменениями.</string>
<string name="warning_database_info_changed">Информация, содержащаяся в файле базы, была изменена вне этого приложения.</string>
<string name="menu_reload_database">Перезагрузить базу</string>
</resources> </resources>

Some files were not shown because too many files have changed in this diff Show More