Compare commits

...

210 Commits

Author SHA1 Message Date
J-Jamet
49bcc877ef Merge branch 'release/2.5RC2' 2020-05-01 13:28:50 +02:00
J-Jamet
4b81dd552e Update screens 2020-05-01 13:11:31 +02:00
J-Jamet
ff63aaf3f3 Remove unused file types 2020-04-30 21:14:15 +02:00
J-Jamet
39cbd1477b Upgrade CHANGELOG 2020-04-30 18:19:37 +02:00
J-Jamet
3741cc558a Remove field reference URL 2020-04-30 18:02:12 +02:00
J-Jamet
2a314ca3c1 Merge branch 'translations' into develop 2020-04-30 17:51:50 +02:00
J-Jamet
10f9564825 Remove unused comment 2020-04-30 17:51:23 +02:00
J-Jamet
1ae87c9b18 Fix small string issues 2020-04-30 17:50:10 +02:00
J-Jamet
24301ba462 Fix string strong tag 2020-04-30 17:43:19 +02:00
J-Jamet
ff6481274f Fix small string bug 2020-04-30 17:36:43 +02:00
J-Jamet
866df585a2 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-04-30 17:24:48 +02:00
J-Jamet
416bec04a1 Fix context 2020-04-30 17:07:52 +02:00
J-Jamet
f747d6725e Add flavor dimension 2020-04-30 17:03:43 +02:00
J-Jamet
8846918e55 Fix file manager dialog 2020-04-30 16:30:52 +02:00
J-Jamet
e347aefcd9 Manual merge of strings #460 2020-04-30 16:04:41 +02:00
J-Jamet
0a0676af3a Android backup management 2020-04-30 15:45:30 +02:00
Mik
7a69b63b4f Translated using Weblate (Italian)
Currently translated at 97.0% (421 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-30 03:32:39 +02:00
J-Jamet
fab5626741 Fix error message in password generator 2020-04-29 17:46:33 +02:00
J-Jamet
c6832d6478 Check permission in onResume 2020-04-29 16:56:47 +02:00
J-Jamet
f451597746 Add thread sleep 2020-04-29 16:35:17 +02:00
J-Jamet
a92e5b5156 Remove dialog task listener (issue dialog still in view) 2020-04-29 15:16:58 +02:00
J-Jamet
786b3b26ea Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2020-04-28 21:42:40 +02:00
J-Jamet
2e9fd2fd79 Merge branch 'feature/fix_opening' into develop 2020-04-28 21:42:18 +02:00
J-Jamet
3be2b9893b Move onActionFinish?.invoke(actionTask, result) 2020-04-26 20:00:47 +02:00
J-Jamet
7f6bed4f5f Replace EditText by TextInputEditText in InputLayout 2020-04-26 10:51:16 +02:00
J-Jamet
37322f284a Merge branch 'develop' into feature/fix_opening 2020-04-26 10:17:46 +02:00
J-Jamet
4106bb1792 Fix lock loop 2020-04-26 10:16:56 +02:00
J-Jamet
229db1242a Listener for task dialog 2020-04-26 09:10:18 +02:00
J-Jamet
8201b42135 Fix progress task dialog 2020-04-25 15:41:33 +02:00
J-Jamet
24818575dc Finish database task only if a binder is attach (to fix too fast opening) 2020-04-25 14:19:50 +02:00
J-Jamet
213050e7f2 Better task listener implementation 2020-04-24 22:09:46 +02:00
J-Jamet
1efaf4e3ea Kotlinized VariantDictionnary and fix database creation 2020-04-24 20:35:36 +02:00
J-Jamet
5532147992 Update CHANGELOG 2020-04-24 18:32:11 +02:00
J-Jamet
550b43094d Fix default username in existing entry #535 2020-04-24 18:30:17 +02:00
J-Jamet
5e831837c8 Update gradle kotlin 2020-04-24 18:20:07 +02:00
J-Jamet
480e88a088 Update gradle 2020-04-24 18:19:35 +02:00
J-Jamet
81cbcbe8af Upgrade TapTargetView lib to 1.13.0 2020-04-24 18:15:17 +02:00
J-Jamet
5649634809 Fix toolbar icon color after theme change 2020-04-24 17:55:44 +02:00
J-Jamet
69a253f738 Remove unused preference theme 2020-04-23 22:39:04 +02:00
J-Jamet
fbc8cfddb8 Fix purple style 2020-04-23 21:59:32 +02:00
J-Jamet
3e1024804c Try to fix preference icons 2020-04-23 21:37:24 +02:00
J-Jamet
cf0b51be00 Fix style purple 2020-04-23 17:53:44 +02:00
J-Jamet
5b295a2a8f Fix pref icon color for KitKat 2020-04-23 15:23:23 +02:00
Andrew
ebb3d7a149 Translated using Weblate (Russian)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-04-23 15:11:22 +02:00
J-Jamet
73ab348d11 Fix keyfile name view 2020-04-23 14:59:21 +02:00
J-Jamet
8ce17086f8 Fix show lock button in setting orientation change 2020-04-23 14:51:32 +02:00
J-Jamet
45cffc93b1 Try to fix theme issue #509 2020-04-23 14:32:59 +02:00
J-Jamet
71cf4d5a34 Fix requireContext, requireArguments and import 2020-04-23 13:43:57 +02:00
J-Jamet
b79a4154af Fix deprecated FragmentManager 2020-04-23 13:35:42 +02:00
J-Jamet
ff818f8472 Upgrade Preference lib 2020-04-23 12:37:12 +02:00
Kunzisoft
b540d32ca3 Added translation using Weblate (Bulgarian) 2020-04-23 09:45:08 +02:00
J-Jamet
895a11d45f Fix opening database wait 2020-04-22 21:07:56 +02:00
J-Jamet
bdafae6132 Fix small warning 2020-04-22 20:50:59 +02:00
J-Jamet
f45d9ce6da Merge branch 'feature/UnsignedValues' into develop 2020-04-22 20:33:35 +02:00
J-Jamet
7456e2c8f5 Refactor key encryption methods and add unit tests 2020-04-22 20:23:23 +02:00
J-Jamet
fc51b50fe6 Fix download_progression string 2020-04-22 18:57:55 +02:00
J-Jamet
b31aa26975 Update CHANGELOG 2020-04-22 18:51:32 +02:00
J-Jamet
49801e1b14 Merge branch 'feature/KeeWeb_Database' into develop 2020-04-22 18:49:50 +02:00
J-Jamet
1453464cbd Remove unused code 2020-04-22 17:46:00 +02:00
J-Jamet
b9be8ff13d Fix KDBX header reader for KeeWeb database #533 2020-04-22 17:30:20 +02:00
J-Jamet
9663c3cadd Add UnsignedInt and UnsignedLong 2020-04-22 15:55:21 +02:00
J-Jamet
da5490bc0a Remove unused import 2020-04-22 15:37:40 +02:00
anonymous
df9b1b9fbb Translated using Weblate (Russian)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-04-22 14:33:57 +02:00
zeritti
6a0a3ded13 Translated using Weblate (Czech)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-04-21 19:11:21 +02:00
J-Jamet
ac7f4a08c3 Replace empty recycle bin icon 2020-04-20 19:12:16 +02:00
J-Jamet
e5ff5e3364 Update CHANGELOG 2020-04-20 19:09:24 +02:00
J-Jamet
f0ac19fcc1 Fix "Go" button #523 2020-04-20 19:07:09 +02:00
J-Jamet
ce23d34923 Fix #530 2020-04-20 18:41:57 +02:00
WaldiS
bdbba87f5a Translated using Weblate (Polish)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-04-20 04:11:21 +02:00
Filippo De Bortoli
fa8ef0477b Translated using Weblate (Italian)
Currently translated at 89.1% (387 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-20 04:11:21 +02:00
Jeannette L
3e9ec4cfb6 Translated using Weblate (Italian)
Currently translated at 89.1% (387 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-20 04:11:20 +02:00
J-Jamet
ad1c9d8129 Merge branch 'feature/SearchParameters' into develop 2020-04-18 13:09:39 +02:00
J-Jamet
7aaac4c13c Encapsulate search and UUID hex string 2020-04-18 13:09:17 +02:00
J-Jamet
a6c71c3d54 StringUtil refactoring 2020-04-18 11:29:51 +02:00
J-Jamet
05714f38dc Add search in url for search info 2020-04-18 11:23:20 +02:00
J-Jamet
4ef5e4d8ae Refactor search parameters and Autofill search 2020-04-17 16:15:55 +02:00
Oğuz Ersen
5fd0e1d6fe Translated using Weblate (Turkish)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-16 13:11:25 +02:00
Allan Nordhøy
97faf5bef3 Translated using Weblate (Norwegian Bokmål)
Currently translated at 79.0% (343 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-04-16 13:11:25 +02:00
Destiny Li
bd77128733 Translated using Weblate (English)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-04-16 13:11:24 +02:00
Destiny Li
935debc6bf Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-04-16 13:11:24 +02:00
solokot
699ed19b6a Translated using Weblate (Russian)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-04-16 13:11:24 +02:00
Jeannette L
126e5a94c2 Translated using Weblate (French)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-04-16 13:11:23 +02:00
sshshsjsjaja
792bd36ea7 Translated using Weblate (German)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-04-16 13:11:23 +02:00
jan madsen
04dfee1b43 Translated using Weblate (Danish)
Currently translated at 99.0% (430 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-04-16 13:11:23 +02:00
Carles Sadurní
6a08cfeea9 Translated using Weblate (Catalan)
Currently translated at 61.0% (265 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-16 13:11:22 +02:00
J-Jamet
29a0f6c9f6 Small amelioration 2020-04-15 20:06:14 +02:00
J-Jamet
e2bdf26d82 Fix autofill crash #520 2020-04-15 14:44:13 +02:00
Kunzisoft
1643949110 Translated using Weblate (French)
Currently translated at 99.7% (433 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-04-15 12:50:29 +02:00
J-Jamet
577dabd727 Better HashedBlockOutputStream implementation 2020-04-15 12:11:45 +02:00
J-Jamet
ed4bd0693f Fix small warning 2020-04-15 12:02:51 +02:00
J-Jamet
0f2982b34d Try to fix bad icon exception 2020-04-15 11:52:22 +02:00
C. Rüdinger
0954094800 Translated using Weblate (German)
Currently translated at 98.6% (428 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-04-14 23:35:52 +02:00
sshshsjsjaja
22dcfafc62 Translated using Weblate (German)
Currently translated at 98.6% (428 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-04-14 23:35:51 +02:00
J-Jamet
dd34051426 Update CHANGELOG 2020-04-14 20:28:25 +02:00
J-Jamet
6feaf2cb8a Update Autofill compatibility 2020-04-14 20:27:08 +02:00
J-Jamet
603f64ea92 Check TODOs 2020-04-14 20:23:50 +02:00
J-Jamet
568f9ab0d0 Check TODOs 2020-04-14 19:54:02 +02:00
J-Jamet
a2ff1c33e6 Update CHANGELOG 2020-04-14 19:33:29 +02:00
J-Jamet
84452e9fc0 Spongy Castle by Bouncy Castle replacement 2020-04-13 13:39:45 +02:00
J-Jamet
5a6ae453cf Upgrade strings comments 2020-04-13 10:03:49 +02:00
Hosted Weblate
cde3e3361d Merge branch 'origin/master' into Weblate. 2020-04-13 08:58:48 +02:00
Oğuz Ersen
25f01192a4 Translated using Weblate (Turkish)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-13 08:58:47 +02:00
Carles Sadurní
bacb08ec65 Translated using Weblate (Catalan)
Currently translated at 16.7% (72 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-13 08:58:47 +02:00
anonymous
36c905ee1c Translated using Weblate (Catalan)
Currently translated at 18.1% (78 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-12 22:44:08 +02:00
Carles Sadurní
6b9280534a Translated using Weblate (Catalan)
Currently translated at 18.1% (78 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-12 22:44:08 +02:00
J-Jamet
f94c6e850f Add History of each entry in description 2020-04-12 21:20:54 +02:00
J-Jamet
f4cecf6906 Upgrade to version 2.5 2020-04-12 20:51:02 +02:00
J-Jamet
a81d3766f8 Merge tag '2.5RC1' into develop
2.5RC1
2020-04-12 20:32:12 +02:00
J-Jamet
7182c2e66d Merge branch 'release/2.5RC1' 2020-04-12 20:32:03 +02:00
J-Jamet
503316bc70 Fix navigation bar color for light theme 2020-04-12 20:08:39 +02:00
J-Jamet
d5af59f2c7 Merge branch 'translations' into release/2.5RC1 2020-04-12 20:03:44 +02:00
J-Jamet
cb3ac1ad3a version 2.5RC1 2020-04-12 19:36:51 +02:00
J-Jamet
e161080e4c Fix special button 2020-04-11 17:36:24 +02:00
J-Jamet
ceab7f917b Upgrade CHANGELOG 2020-04-11 16:21:27 +02:00
J-Jamet
41d8066c4c Fix autofill bad recognition for key-value format #513 2020-04-11 16:16:50 +02:00
J-Jamet
e373cbd776 File manager explanation link 2020-04-11 12:23:46 +02:00
J-Jamet
05ea6b6b10 Change background color and fix bottom button 2020-04-10 20:37:56 +02:00
J-Jamet
28eed3ae71 New file selection layout 2020-04-10 20:15:08 +02:00
Mert Sezer
535c67ac9b Translated using Weblate (Turkish)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-10 16:09:45 +02:00
J-Jamet
68027a6e15 Linkfy notes #426 2020-04-10 13:22:26 +02:00
J-Jamet
12dea6b499 Add new autofill hint for username 2020-04-10 12:32:38 +02:00
J-Jamet
d8bd078a02 Fix custom fields duplication #257 2020-04-10 11:32:23 +02:00
J-Jamet
439bc109b0 Update CHANGELOG 2020-04-09 21:42:16 +02:00
J-Jamet
b1ec93ceb5 Add navigation bar color #510 2020-04-09 21:38:32 +02:00
J-Jamet
44b9aa0e48 Style black as separate file 2020-04-09 21:31:48 +02:00
J-Jamet
0a59063027 Add scrollbar in nodes list 2020-04-09 20:33:12 +02:00
Oğuz Ersen
5db4608abd Translated using Weblate (Turkish)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-09 06:45:37 +02:00
J-Jamet
6ece2aa6cb Add autofill web domain and app id 2020-04-08 20:50:28 +02:00
J-Jamet
95ee45f666 Merge branch 'feature/Autofill_Domain' into develop 2020-04-08 17:55:42 +02:00
J-Jamet
556e90b8d8 Fix autofill response 2020-04-08 17:17:29 +02:00
J-Jamet
42841e6247 Setting for autofill automatic search 2020-04-08 16:55:30 +02:00
J-Jamet
324c82248a Refactor magikeyboard settings and add autofill settings 2020-04-08 15:44:14 +02:00
J-Jamet
94f5a47918 Catch exception during autofill parse 2020-04-08 14:11:18 +02:00
J-Jamet
3f70990956 Better autofill workflow and add autofill icon for each entry 2020-04-08 13:02:15 +02:00
J-Jamet
d77635e572 Fix autofill auto search 2020-04-07 21:01:26 +02:00
solokot
0f26f1b751 Translated using Weblate (Russian)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-04-07 01:36:50 +02:00
J-Jamet
456bc22138 Encapsulate search methods and search without UI 2020-04-06 21:43:55 +02:00
J-Jamet
b6fe91e396 Fix small warning 2020-04-06 21:36:50 +02:00
J-Jamet
0fb4d26949 Add auto search info 2020-04-06 17:36:51 +02:00
J-Jamet
3b3583a416 Retrieve application id and domain 2020-04-06 11:41:16 +02:00
J-Jamet
cac1d576c8 Upgrade Autofill algorithm 2020-04-06 10:33:44 +02:00
J-Jamet
9cba0d0a48 Message to indicate file revoked 2020-04-06 10:12:09 +02:00
J-Jamet
3bf11e9dc0 Remove unused import 2020-04-06 09:41:02 +02:00
Fabio Iacovino
edbb160ac6 Translated using Weblate (Italian)
Currently translated at 90.4% (388 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-06 00:22:25 +02:00
WaldiS
ee111dc63c Translated using Weblate (Polish)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-04-05 17:58:26 +02:00
Filippo De Bortoli
b06edb756a Translated using Weblate (Italian)
Currently translated at 89.7% (385 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-05 17:13:40 +02:00
anonymous
fd745494e0 Translated using Weblate (Italian)
Currently translated at 89.7% (385 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-05 17:13:40 +02:00
Fabio Iacovino
702bf3f479 Translated using Weblate (Italian)
Currently translated at 89.7% (385 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-05 17:13:40 +02:00
Fabio Iacovino
6adc02a91f Translated using Weblate (Italian)
Currently translated at 85.3% (366 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-05 17:07:29 +02:00
anonymous
55bae5a130 Translated using Weblate (Italian)
Currently translated at 85.3% (366 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-05 17:07:29 +02:00
Filippo De Bortoli
1aae817b17 Translated using Weblate (Italian)
Currently translated at 85.3% (366 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-05 17:07:29 +02:00
Oğuz Ersen
8afc8c23fb Translated using Weblate (Turkish)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-05 03:01:39 +02:00
Allan Nordhøy
36e473b139 Translated using Weblate (Norwegian Bokmål)
Currently translated at 76.6% (329 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-04-05 03:01:38 +02:00
Allan Nordhøy
e529723f86 Translated using Weblate (English)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-04-05 03:01:38 +02:00
Dwhite Reeves
6c5112c142 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-04-05 03:01:38 +02:00
Allan Nordhøy
77ac68a603 Translated using Weblate (Dutch)
Currently translated at 77.6% (333 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-04-05 03:01:37 +02:00
Éfrit
e816f40872 Translated using Weblate (French)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-04-05 03:01:37 +02:00
zeritti
65313f114b Translated using Weblate (Czech)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-04-05 03:01:37 +02:00
J-Jamet
ef1f1342f5 Upgrade CHANGELOG 2020-04-04 18:49:22 +02:00
J-Jamet
9205fe6c08 Merge branch 'feature/Lock_Button' into develop 2020-04-04 18:43:03 +02:00
J-Jamet
75df3e81fe Fix style 2020-04-04 18:42:35 +02:00
J-Jamet
47fffbadb5 Setting to show the lock button 2020-04-04 18:25:48 +02:00
J-Jamet
78354473fa Fix lock button color and education screen 2020-04-04 17:53:04 +02:00
J-Jamet
8d7efb44b5 Change lock button color 2020-04-03 22:21:50 +02:00
J-Jamet
cab00b3d8c Change lock button color 2020-04-03 22:18:33 +02:00
Mert Sezer
8efab01336 Translated using Weblate (Turkish)
Currently translated at 99.5% (427 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-03 21:41:26 +02:00
Oğuz Ersen
66feb8beb4 Translated using Weblate (Turkish)
Currently translated at 99.5% (427 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-03 21:41:26 +02:00
Mert Sezer
ca4cccffeb Translated using Weblate (Turkish)
Currently translated at 99.3% (426 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-03 21:40:34 +02:00
Oğuz Ersen
51b1760c50 Translated using Weblate (Turkish)
Currently translated at 99.3% (426 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-03 21:40:33 +02:00
J-Jamet
42e1bda365 Change lock button color 2020-04-03 21:32:32 +02:00
J-Jamet
194b6b557a Add lock button elevation and border 2020-04-03 21:17:38 +02:00
Dwhite Reeves
e7bc439997 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-04-03 15:44:41 +02:00
Allan Nordhøy
ef76cce0ac Translated using Weblate (Romanian)
Currently translated at 95.3% (409 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2020-04-03 13:26:34 +02:00
Allan Nordhøy
63eec6d969 Translated using Weblate (Turkish)
Currently translated at 96.9% (416 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-03 13:26:34 +02:00
WaldiS
719ae74c06 Translated using Weblate (Polish)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-04-03 13:26:33 +02:00
Mert Sezer
37cf424eb8 Translated using Weblate (Turkish)
Currently translated at 96.9% (416 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-01 17:46:51 +02:00
anonymous
3a87f7ba9d Translated using Weblate (Turkish)
Currently translated at 96.9% (416 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-01 17:46:50 +02:00
J-Jamet
275428d825 First commit for new lock button 2020-04-01 14:57:31 +02:00
Pavel Borecki
7b9dac86ca Translated using Weblate (Czech)
Currently translated at 96.7% (415 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-04-01 03:28:42 +02:00
J-Jamet
46496ee2cc Merge branch 'damoasda-translation-update' into develop 2020-03-31 21:58:21 +02:00
J-Jamet
110cc402cc Add keyfile selection view 2020-03-31 21:48:51 +02:00
J-Jamet
587f006259 Long click to use GET_CONTENT 2020-03-31 19:46:43 +02:00
Moasda
ec45c0df81 Translated using Weblate (German)
Currently translated at 99.7% (428 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-30 22:24:51 +02:00
jan madsen
dc575aeca4 Translated using Weblate (Danish)
Currently translated at 98.3% (422 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-03-30 22:24:51 +02:00
Herbert Reiter
a3f790f000 Fix inconsistencies with translation arguments (e.g. %1$s), escape % character 2020-03-30 22:08:50 +02:00
Herbert Reiter
4e9e188d02 Add and fix German translations 2020-03-30 22:03:09 +02:00
J-Jamet
abb17efae4 Merge branch 'feature/File_Manager_Access' into develop 2020-03-30 18:17:24 +02:00
Hosted Weblate
16be990502 Merge branch 'origin/master' into Weblate. 2020-03-30 08:48:35 +02:00
anonymous
d1a496f9a3 Translated using Weblate (German)
Currently translated at 99.7% (428 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-30 03:17:18 +02:00
Allan Nordhøy
cd730fcfef Translated using Weblate (Norwegian Bokmål)
Currently translated at 76.9% (330 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-03-29 23:06:21 +02:00
Moasda
1b65bf665b Translated using Weblate (German)
Currently translated at 99.7% (428 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-29 23:06:21 +02:00
J-Jamet
6f4735790c Fix autofill for Android app #502 2020-03-29 22:00:24 +02:00
J-Jamet
040666f89d Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2020-03-29 20:19:42 +02:00
J-Jamet
227cb800c3 Merge branch 'damoasda-readme-fix' 2020-03-29 16:15:44 +02:00
Herbert Reiter
0052569a14 Fix FAQ link to wiki page 2020-03-29 15:41:49 +02:00
J-Jamet
f7561c4888 Upgrade to version 2.5beta31 2020-03-29 15:22:34 +02:00
Destiny Li
92200f19e7 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-03-29 14:09:36 +02:00
Éfrit
4c01f18a62 Translated using Weblate (French)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-03-29 14:09:36 +02:00
anonymous
a6ce3e49fe Translated using Weblate (German)
Currently translated at 99.5% (427 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-29 14:09:36 +02:00
Oliver
1b3a5d1bf6 Translated using Weblate (German)
Currently translated at 99.5% (427 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-29 14:09:35 +02:00
J-Jamet
db7bdc63c8 Fix #501 2020-03-28 18:13:09 +01:00
J-Jamet
7aca550f02 Merge branch 'develop' into feature/File_Manager_Access 2020-03-28 17:47:14 +01:00
J-Jamet
b60980b3fd Upgrade to 2.5beta31 2020-03-28 17:46:50 +01:00
J-Jamet
b578c2c584 Add write permission 2020-03-28 17:30:57 +01:00
solokot
fc45bd624e Translated using Weblate (Russian)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-03-27 20:06:43 +01:00
anonymous
17be3d9d2c Translated using Weblate (Russian)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-03-27 20:03:48 +01:00
solokot
5b3a38a7bc Translated using Weblate (Russian)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-03-27 20:03:47 +01:00
Hosted Weblate
4403835d50 Merge branch 'origin/master' into Weblate. 2020-03-27 18:36:55 +01:00
C. Rüdinger
b7a3d3eb46 Translated using Weblate (German)
Currently translated at 99.5% (424 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-27 18:36:54 +01:00
J-Jamet
29846b22fe Merge tag '2.5beta30' into develop
2.5beta30
2020-03-27 18:13:28 +01:00
254 changed files with 4225 additions and 2827 deletions

View File

@@ -1,3 +1,23 @@
KeePassDX(2.5RC2)
* Replacement of Spongy Castle by Bouncy Castle
* Update Autofill compatibility
* Fix Magikeyboard "Go" action
* Fix KeeWeb database opening
* Fix default username
* Fix themes
* Fix small issues
KeePassDX(2.5RC1)
* Add write permission to keep compatibility with old file managers
* Fix autofill for apps
* Auto search for autofill
* New keyfile input
* Icon to hide keyfile input
* New lock button
* Setting to hide lock button in user interface
* Clickable links in notes
* Fix autofill for key-value pairs
KeePassDX(2.5beta30) KeePassDX(2.5beta30)
* Fix Lock after screen off (wait 1.5 seconds) * Fix Lock after screen off (wait 1.5 seconds)
* Upgrade autofill algorithm * Upgrade autofill algorithm

View File

@@ -0,0 +1,177 @@
Terms of Service
This is the terms of service for the Android Backup Service.
1. Your relationship with Google
1.1 Your use of the Android Backup Service (referred to as the "Service" in this document) is subject to the terms of a legal agreement between you and Google. "Google" means Google LLC, whose principal place of business is at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. This document explains how the agreement is made up, and sets out some of the terms of that agreement.
1.2 Unless otherwise agreed in writing with Google, your agreement with Google will always include, at a minimum, the terms and conditions set out in this document. These are referred to below as the "Terms".
1.3 The Terms form a legally binding agreement between you and Google in relation to your use of the Service. It is important that you take the time to read them carefully.
2. Accepting the Terms
2.1 In order to use the Service, you must first agree to the Terms. You may not use the Service if you do not accept the Terms.
2.2 You can accept the Terms by clicking to accept or agree to the Terms, where this option is made available to you by Google.
2.3 You may not use the Service and may not accept the Terms if you are not of legal age to form a binding contract with Google.
2.4 You represent that you have full power, capacity and authority to accept these Terms. If you are accepting on behalf of your employer or another entity, you represent that you have full legal authority to bind your employer or such entity to these Terms. If you don't have the legal authority to bind, please ensure that an authorized person from your entity consents to and accepts these Terms.
3. Provision of the Service by Google
3.1 Google has subsidiaries and affiliated legal entities around the world ("Subsidiaries and Affiliates"). Sometimes, these companies will be providing the Service to you on behalf of Google itself. You acknowledge and agree that Subsidiaries and Affiliates will be entitled to provide the Service to you.
3.2 Google is constantly innovating in order to provide the best possible experience for its users. You acknowledge and agree that the form and nature of the Service which Google provides may change from time to time without prior notice to you.
3.3 As part of this continuing innovation, you acknowledge and agree that Google may stop (permanently or temporarily) providing the Service (or any features within the Service) to you or to users generally at Google's sole discretion, without prior notice to you. You may stop using the Service at any time. You do not need to specifically inform Google when you stop using the Service.
3.4 You acknowledge and agree that if Google disables your Backup Service Key, you and the Android application(s) you developed ("Application(s)") may be prevented from accessing the Service and any content that is stored with the Service.
3.5 You acknowledge and agree that Google may set a fixed upper limit on the number of backup transmissions you may send or receive through the Service or on the amount of storage space used for the provision of the Service at any time, at Google's discretion. You agree to abide by any such fixed upper limits.
4. Use of the Service by you
4.1 In order to access the Service, you must have a unique application identifier ("Package Name") for your Application as described in the documentation for the Service.
4.2 After supplying Google with the Package Name and accepting the Terms, you will be issued an alphanumeric key ("Backup Service Key") assigned to you by Google that is uniquely associated with your Application. Your Application must include this Backup Service Key as described in the documentation for the Service.
4.3 There is currently no limit to the number of Backup Service Keys you may obtain in this manner provided that you use a different Package Name for each Backup Service Key you obtain. You agree that each Backup Service Key is only valid for Applications with the corresponding Package Name. You agree that Google may, in its sole discretion, impose a limit on the number of Backup Service Keys that may be obtained in the future. You agree that your continued use of any of the Backup Service Keys assigned by Google, or distribution of any Applications using such Backup Service Keys, constitutes your continued agreement to these Terms.
4.4 You agree to use the Service only for purposes that are permitted by (a) the Terms and (b) any applicable law, regulation, third-party terms of service, or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
4.5 You agree not to access (or attempt to access) any of the Service by any means other than through the interfaces, methods, and APIs that are provided by Google, unless you have been specifically allowed to do so in a separate agreement with Google.
4.6 You agree that you will not engage in any activity that interferes with or disrupts the Service (or the servers and networks which are connected to the Service), or the servers or networks of any third-party.
4.7 You agree that your use of the Service will be in compliance with any documentation guidelines provided by Google and that failure to comply with the documentation guidelines may result in the disabling of the Backup Service Key(s) for your Application(s).
4.8 Unless you have been specifically permitted to do so in a separate agreement with Google, you agree that you will not reproduce, duplicate, copy, sell, trade or resell (a) use of the Service, or (b) access to the Service.
4.9 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) your and your Application's use of the Service, any breach of your obligations under the Terms, and for the consequences (including any loss or damage which Google may suffer) of any such breach.
4.10 You agree that in your use of the Service, you and your Applications will protect the privacy and legal rights of users. You must provide legally adequate privacy notice and protection for users whose data your Applications back up to the Service. Further, your Application may only use that information for the limited purpose of backing up the data to the Service unless the user has given you permission for further use. If the user has not given you permission to back up information to the Service, you may not transmit such information to the Service.
4.11 You agree that you and your Applications will not transmit or store sensitive user information, such as user names, passwords, or credit card numbers, through the Service.
5. Security
5.1 You agree and understand that you are responsible for maintaining the security associated with any information you provide to access the Service as well as of the Backup Service Key(s) assigned to you by Google. You agree that only you are authorized to use the Backup Service Key(s) assigned to you.
5.2 Accordingly, you agree that you will be solely responsible to Google for all activities that occur in connection with your access to the Service, as well as the Backup Service Key.
5.3 If you become aware of any unauthorized use of your Backup Service Key(s) you agree to notify Google immediately.
6. Privacy and your personal information
6.1 For information about Google's data protection practices, please read Google's privacy policy at http://www.google.com/privacy.html. This policy explains how Google treats your personal information when you use the Service.
6.2 You agree to the use of your data in accordance with Google's privacy policies.
7. Content in the Service
7.1 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any Content that you or your Applications transmit or store through the Service and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. You agree that you are solely responsible for (A) any Content that is transmitted through the Service by your Applications, and (B) any Content that Devices retrieve from the Service by virtue of your Applications. For purposes of the Terms, "Content" means information such as data, messages, settings information, written text, computer software, music, audio files or other sounds, photographs, videos or other images. "Device(s)" means device(s) powered by the Android operating system.
7.2 You agree that you will not transmit any Content through the Service that is copyrighted, protected by trade secret or otherwise subject to third party proprietary rights, including patent, privacy and publicity rights, unless you are the owner of such rights or have permission from their rightful owner to transmit the Content through the Service.
8. Proprietary rights
8.1 You acknowledge and agree that Google (or Google's licensors) own all legal right, title and interest in and to the Service, including any intellectual property rights which subsist in the Service (whether those rights happen to be registered or not, and wherever in the world those rights may exist).
8.2 Unless you have agreed otherwise in writing with Google, nothing in the Terms gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, and other distinctive brand features.
8.3 If you have been given an explicit right to use any of these brand features in a separate written agreement with Google, then you agree that your use of such features shall be in compliance with that agreement, any applicable provisions of the Terms, and Google's brand feature use guidelines as updated from time to time. These guidelines can be viewed online at http://www.google.com/permissions/ guidelines.html (or such other URL as Google may provide for this purpose from time to time).
8.4 You agree that you shall not remove, obscure, or alter any proprietary rights notices (including copyright, trade mark notices) which may be affixed to or contained within the Service.
9. License from Google
9.1 Subject to terms and conditions of these Terms, Google gives you a personal, worldwide, royalty-free, non-assignable and non-exclusive license to use the Service as provided to you by Google. This license is for the sole purpose of enabling you to use and enjoy the benefit of the Service as provided by Google, in the manner permitted by the Terms.
9.2 You may not (and you may not permit anyone else to) copy, modify, create a derivative work of, reverse engineer, decompile or otherwise attempt to extract the source code from the Service or any part thereof, unless this is expressly permitted or required by law, or unless you have been specifically told that you may do so by Google, in writing.
9.3 Unless Google has given you specific written permission to do so, you may not assign (or grant a sub-license of) your rights to use the Service, grant a security interest in or over your rights to use the Service, or otherwise transfer any part of your rights to use the Service.
10. Your code
10.1 Google claims no ownership or control over any source code written by you to be used with the Service. You retain copyright and any other rights you already hold in this code, and you are responsible for protecting those rights, as appropriate.
11. Ending your relationship with Google
11.1 The Terms will continue to apply until terminated by either you or Google as set out below.
11.2 You may terminate your legal agreement with Google by discontinuing your use of the Service at any time.
11.3 Google may, at any time, terminate its legal agreement with you if:
(A) you have breached any provision of the Terms (or have acted in manner which clearly shows that you do not intend to, or are unable to comply with the provisions of the Terms); or
(B) Google is required to do so by law (for example, where the provision of the Service to you is, or becomes, unlawful); or
(C) Google is transitioning to no longer providing the Service; or
(D) your Application fails to meet the documentation guidelines provided by Google.
11.4 Nothing in this Section shall affect Google's rights regarding provision of the Service under Section 3 of the Terms.
11.5 When these Terms come to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the Terms have been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of Sections 12, 13 and Paragraph 16 shall continue to apply to such rights, obligations and liabilities indefinitely.
12. EXCLUSION OF WARRANTIES
12.1 NOTHING IN THESE TERMS, INCLUDING SECTIONS 12 AND 13, SHALL EXCLUDE OR LIMIT GOOGLE'S WARRANTY OR LIABILITY FOR LOSSES WHICH MAY NOT BE LAWFULLY EXCLUDED OR LIMITED BY APPLICABLE LAW. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR CONDITIONS OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR LOSS OR DAMAGE CAUSED BY NEGLIGENCE, BREACH OF CONTRACT OR BREACH OF IMPLIED TERMS, OR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, ONLY THE LIMITATIONS WHICH ARE LAWFUL IN YOUR JURISDICTION WILL APPLY TO YOU AND OUR LIABILITY WILL BE LIMITED TO THE MAXIMUM EXTENT PERMITTED BY LAW.
12.2 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SERVICE IS AT YOUR SOLE RISK AND THAT THE SERVICE AND CONTENT ARE PROVIDED "AS IS" AND "AS AVAILABLE".
12.3 IN PARTICULAR, GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS DO NOT REPRESENT OR WARRANT TO YOU THAT:
(A) YOUR USE OF THE SERVICE WILL MEET YOUR REQUIREMENTS,
(B) YOUR USE OF THE SERVICE WILL BE UNINTERRUPTED, TIMELY, SECURE OR FREE FROM ERROR, AND
(C) THAT DEFECTS IN THE OPERATION OR FUNCTIONALITY OF ANY SOFTWARE PROVIDED TO YOU AS PART OF THE SERVICE WILL BE CORRECTED.
12.4 NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM GOOGLE OR THROUGH OR FROM THE SERVICE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THE TERMS.
12.5 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
13. LIMITATION OF LIABILITY
13.1 SUBJECT TO OVERALL PROVISION IN PARAGRAPH 12.1 ABOVE, YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU FOR:
(A) ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL CONSEQUENTIAL OR EXEMPLARY DAMAGES WHICH MAY BE INCURRED BY YOU, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY. THIS SHALL INCLUDE, BUT NOT BE LIMITED TO, ANY LOSS OF PROFIT (WHETHER INCURRED DIRECTLY OR INDIRECTLY), ANY LOSS OF GOODWILL OR BUSINESS REPUTATION, ANY LOSS OF DATA SUFFERED, COST OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICE, OR OTHER INTANGIBLE LOSS;
(B) ANY LOSS OR DAMAGE WHICH MAY BE INCURRED BY YOU, INCLUDING BUT NOT LIMITED TO LOSS OR DAMAGE AS A RESULT OF:
(I) ANY CHANGES WHICH GOOGLE MAY MAKE TO THE SERVICE, OR FOR ANY PERMANENT OR TEMPORARY CESSATION IN THE PROVISION OF THE SERVICE (OR ANY FEATURES WITHIN THE SERVICE);
(II) THE DELETION OF, CORRUPTION OF, OR FAILURE TO STORE, ANY CONTENT AND OTHER COMMUNICATIONS DATA MAINTAINED OR TRANSMITTED BY OR THROUGH YOUR USE OF THE SERVICE;
(III) YOUR FAILURE TO PROVIDE GOOGLE WITH ACCURATE ACCOUNT INFORMATION; OR
(IV) YOUR FAILURE TO KEEP YOUR PASSWORD OR ACCOUNT DETAILS SECURE AND CONFIDENTIAL.
13.2 THE LIMITATIONS ON GOOGLE'S LIABILITY TO YOU IN PARAGRAPH 13.1 ABOVE SHALL APPLY WHETHER OR NOT GOOGLE HAS BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
14. Indemnification
14.1 You agree to hold harmless and indemnify Google, and its subsidiaries, affiliates, officers, agents, employees, or licensors from and against any third party claim arising from or in any way related to (a) your breach of the Terms, (b) your use of the Service, or (c) your violation of applicable laws, rules or regulations in connection with the Service, including any liability or expense arising from all claims, losses, damages (actual and consequential), suits, judgments, litigation costs and attorneys' fees, of every kind and nature. In such a case, Google will provide you with written notice of such claim, suit or action.
15. Changes to the Terms
15.1 Due to things like changes to the law or changes to functionality offered through the Service, Google may need to change these Terms from time to time. You should look at the Terms regularly. We'll post notice of the modified Terms within, or through, the Service. Once the modified Terms are posted, the changes will become effective immediately, and you are deemed to have accepted the modified Terms if you continue to use the Service. If you do not agree to the modified Terms for the Service, please stop using the Service.
16. General legal terms
16.1 The Terms constitute the whole legal agreement between you and Google and govern your use of the Service (but excluding any service which Google may provide to you under a separate written agreement), and completely replace any prior agreements between you and Google in relation to the Service.
16.2 You agree that Google may provide you with notices, including those regarding changes to the Terms, by email, regular mail, or postings on the Service.
16.3 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the Terms (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
16.4 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of these Terms is invalid, then that provision will be removed from the Terms without affecting the rest of the Terms. The remaining provisions of the Terms will continue to be valid and enforceable.
16.5 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the Terms and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the Terms which confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the Terms.
16.6 The Terms, and your relationship with Google under the Terms, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the Terms. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.

View File

@@ -15,6 +15,7 @@
* Material design with **themes** * Material design with **themes**
* **AutoFill** and Integration * **AutoFill** and Integration
* Field filling **keyboard** * Field filling **keyboard**
* **History** of each entry
* Precise management of **settings** * Precise management of **settings**
* Code written in **native language** *(Kotlin / Java / JNI / C)* * Code written in **native language** *(Kotlin / Java / JNI / C)*
@@ -53,9 +54,9 @@ You can contribute in different ways to help us on our work.
alt="Get it on Google Play" alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free) height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
## F.A.Q. ## Frequently Asked Questions
Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassDX/wiki/F.A.Q.) Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
## Other devices ## Other devices

View File

@@ -11,14 +11,15 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 29 targetSdkVersion 29
versionCode = 30 versionCode = 32
versionName = "2.5beta30" versionName = "2.5RC2"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"
testInstrumentationRunner = "android.test.InstrumentationTestRunner" testInstrumentationRunner = "android.test.InstrumentationTestRunner"
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}" buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"" ]
kapt { kapt {
arguments { arguments {
@@ -44,32 +45,36 @@ android {
dexOptions { dexOptions {
} }
flavorDimensions "tier" flavorDimensions "version"
productFlavors { productFlavors {
libre { libre {
dimension "version"
applicationIdSuffix = ".libre" applicationIdSuffix = ".libre"
buildConfigField "String", "BUILD_VERSION", "\"libre\"" buildConfigField "String", "BUILD_VERSION", "\"libre\""
buildConfigField "boolean", "FULL_VERSION", "true" buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "false" buildConfigField "boolean", "CLOSED_STORE", "false"
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}" buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}" buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
} }
pro { pro {
dimension "version"
applicationIdSuffix = ".pro" applicationIdSuffix = ".pro"
buildConfigField "String", "BUILD_VERSION", "\"pro\"" buildConfigField "String", "BUILD_VERSION", "\"pro\""
buildConfigField "boolean", "FULL_VERSION", "true" buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "true" buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", "{}" buildConfigField "String[]", "STYLES_DISABLED", "{}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}" buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIZiXvrQCzSV9LNI6-p7cjTKENZLHIrz_zaqZuQQ" ]
} }
free { free {
dimension "version"
applicationIdSuffix = ".free" applicationIdSuffix = ".free"
buildConfigField "String", "BUILD_VERSION", "\"free\"" buildConfigField "String", "BUILD_VERSION", "\"free\""
buildConfigField "boolean", "FULL_VERSION", "false" buildConfigField "boolean", "FULL_VERSION", "false"
buildConfigField "boolean", "CLOSED_STORE", "true" buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}" buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}" buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIbRfbV8fHLItXo8OcHwrO0sSNblqhPwkc0DPTqg" ]
} }
} }
@@ -85,32 +90,31 @@ android {
} }
} }
def spongycastleVersion = "1.58.0.0"
def room_version = "2.2.5" def room_version = "2.2.5"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0' implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.0.1' implementation 'androidx.biometric:biometric:1.0.1'
implementation "androidx.core:core-ktx:1.2.0"
// To upgrade with style // To upgrade with style
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
// 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"
// Crypto
implementation "com.madgag.spongycastle:core:$spongycastleVersion" implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Time // Time
implementation 'joda-time:joda-time:2.9.9' implementation 'joda-time:joda-time:2.9.9'
// Color // Color
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3' implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
// Education // Education
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0' implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.0'
// Apache Commons Collections // Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1' implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2' implementation 'org.apache.commons:commons-io:1.3.2'
@@ -119,4 +123,7 @@ dependencies {
// 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')
// Tests
androidTestImplementation 'junit:junit:4.12'
} }

View File

@@ -26,11 +26,11 @@ import java.util.Random
import junit.framework.TestCase import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
class FinalKeyTest : TestCase() { class AESKeyTest : TestCase() {
private var mRand: Random? = null private lateinit var mRand: Random
@Throws(Exception::class) @Throws(Exception::class)
override fun setUp() { override fun setUp() {
@@ -40,29 +40,28 @@ class FinalKeyTest : TestCase() {
} }
@Throws(IOException::class) @Throws(IOException::class)
fun testNativeAndroid() { fun testAES() {
// Test both an old and an even number to test my flip variable // Test both an old and an even number to test my flip variable
testNativeFinalKey(5) testAESFinalKey(5)
testNativeFinalKey(6) testAESFinalKey(6)
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun testNativeFinalKey(rounds: Int) { private fun testAESFinalKey(rounds: Long) {
val seed = ByteArray(32) val seed = ByteArray(32)
val key = ByteArray(32) val key = ByteArray(32)
val nativeKey: ByteArray val nativeKey: ByteArray?
val androidKey: ByteArray val androidKey: ByteArray?
mRand!!.nextBytes(seed) mRand.nextBytes(seed)
mRand!!.nextBytes(key) mRand.nextBytes(key)
val aKey = AndroidFinalKey() val androidAESKey = AndroidAESKeyTransformer()
androidKey = aKey.transformMasterKey(seed, key, rounds.toLong()) androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
val nKey = NativeFinalKey() val nativeAESKey = NativeAESKeyTransformer()
nativeKey = nKey.transformMasterKey(seed, key, rounds.toLong()) nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
assertArrayEquals("Does not match", androidKey, nativeKey) assertArrayEquals("Does not match", androidKey, nativeKey)
} }
} }

View File

@@ -80,6 +80,4 @@ class AESTest : TestCase() {
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative) assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
} }
} }

View File

@@ -33,7 +33,7 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.stream.HashedBlockInputStream import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HashedBlockOutputStream import com.kunzisoft.keepass.stream.HashedBlockOutputStream
class HashedBlock : TestCase() { class HashedBlockTest : TestCase() {
@Throws(IOException::class) @Throws(IOException::class)
fun testBlockAligned() { fun testBlockAligned() {

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2017 Brian Pellin, 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.tests.utils
import java.util.Locale
import com.kunzisoft.keepass.utils.StringUtil
import junit.framework.TestCase
class StringUtilTest : TestCase() {
private val text = "AbCdEfGhIj"
private val search = "BcDe"
private val badSearch = "Ed"
private val repText = "AbCtestingaBc"
private val repSearch = "ABc"
private val repSearchBad = "CCCCCC"
private val repNew = "12345"
private val repResult = "12345testing12345"
fun testIndexOfIgnoreCase1() {
assertEquals(1, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH))
}
fun testIndexOfIgnoreCase2() {
assertEquals(-1f, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH).toFloat(), 2f)
}
fun testIndexOfIgnoreCase3() {
assertEquals(-1, StringUtil.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH))
}
fun testReplaceAllIgnoresCase1() {
assertEquals(repResult, StringUtil.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH))
}
fun testReplaceAllIgnoresCase2() {
assertEquals(repText, StringUtil.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH))
}
}

View File

@@ -0,0 +1,28 @@
package com.kunzisoft.keepass.tests.utils
import com.kunzisoft.keepass.utils.UnsignedInt
import junit.framework.TestCase
class UnsignedIntTest: TestCase() {
fun testUInt() {
val standardInt = UnsignedInt(15).toInt()
assertEquals(15, standardInt)
val unsignedInt = UnsignedInt(-1).toLong()
assertEquals(4294967295L, unsignedInt)
}
fun testMaxValue() {
val maxValue = UnsignedInt.MAX_VALUE.toLong()
assertEquals(4294967295L, maxValue)
val longValue = UnsignedInt.fromLong(4294967295L).toLong()
assertEquals(longValue, maxValue)
}
fun testLong() {
val longValue = UnsignedInt.fromLong(50L).toInt()
assertEquals(50, longValue)
val uIntLongValue = UnsignedInt.fromLong(4294967290).toLong()
assertEquals(4294967290, uIntLongValue)
}
}

View File

@@ -17,17 +17,18 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.tests package com.kunzisoft.keepass.tests.utils
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import junit.framework.TestCase import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.util.* import java.util.*
class StringDatabaseKDBUtilsTest : TestCase() { class ValuesTest : TestCase() {
fun testReadWriteLongZero() { fun testReadWriteLongZero() {
testReadWriteLong(0.toByte()) testReadWriteLong(0.toByte())
@@ -77,11 +78,10 @@ class StringDatabaseKDBUtilsTest : TestCase() {
setArray(orig, value, 4) setArray(orig, value, 4)
val one = bytes4ToInt(orig) val one = bytes4ToUInt(orig)
val dest = intTo4Bytes(one) val dest = uIntTo4Bytes(one)
assertArrayEquals(orig, dest) assertArrayEquals(orig, dest)
} }
private fun setArray(buf: ByteArray, value: Byte, size: Int) { private fun setArray(buf: ByteArray, value: Byte, size: Int) {
@@ -133,7 +133,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
} }
private fun testReadWriteByte(value: Byte) { private fun testReadWriteByte(value: Byte) {
val dest: Byte = uIntToByte(byteToUInt(value)) val dest: Byte = UnsignedInt(UnsignedInt.fromByte(value)).toByte()
assert(value == dest) assert(value == dest)
} }
@@ -185,7 +185,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
val leos = LittleEndianDataOutputStream(bos) val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(ULONG_MAX_VALUE) leos.writeLong(UnsignedLong.MAX_VALUE)
leos.close() leos.close()
val uLongMax = bos.toByteArray() val uLongMax = bos.toByteArray()

View File

@@ -15,7 +15,6 @@
<uses-permission <uses-permission
android:name="android.permission.VIBRATE"/> android:name="android.permission.VIBRATE"/>
<uses-permission <uses-permission
android:maxSdkVersion="18"
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application <application
@@ -24,17 +23,15 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:name="com.kunzisoft.keepass.app.App" android:name="com.kunzisoft.keepass.app.App"
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup" android:fullBackupContent="@xml/backup_rules"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true" android:largeHeap="true"
android:resizeableActivity="true" android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night" android:theme="@style/KeepassDXStyle.Night"
tools:targetApi="n"> tools:targetApi="n">
<!-- TODO backup API Key -->
<meta-data <meta-data
android:name="com.google.android.backup.api_key" android:name="com.google.android.backup.api_key"
android:value="" /> android:value="${googleAndroidBackupAPIKey}" />
<activity <activity
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity" android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
android:theme="@style/KeepassDXStyle.SplashScreen" android:theme="@style/KeepassDXStyle.SplashScreen"
@@ -138,12 +135,12 @@
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity" <activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
android:configChanges="keyboardHidden" /> android:configChanges="keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" /> <activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" /> <activity android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity" <activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
android:label="@string/keyboard_name" android:label="@string/keyboard_name"
android:exported="true"> android:exported="true">
</activity> </activity>
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings" <activity android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
android:label="@string/keyboard_setting_label"> android:label="@string/keyboard_setting_label">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>

View File

@@ -52,7 +52,6 @@ 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_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
@@ -73,6 +72,7 @@ class EntryActivity : LockingActivity() {
private var historyView: View? = null private var historyView: View? = null
private var entryContentsView: EntryContentsView? = null private var entryContentsView: EntryContentsView? = null
private var entryProgress: ProgressBar? = null private var entryProgress: ProgressBar? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
private var mDatabase: Database? = null private var mDatabase: Database? = null
@@ -124,6 +124,11 @@ class EntryActivity : LockingActivity() {
entryContentsView = findViewById(R.id.entry_contents) entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this)) entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryProgress = findViewById(R.id.entry_progress) entryProgress = findViewById(R.id.entry_progress)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
// Init the clipboard helper // Init the clipboard helper
clipboardHelper = ClipboardHelper(this) clipboardHelper = ClipboardHelper(this)
@@ -148,6 +153,13 @@ class EntryActivity : LockingActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// Show the lock button
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
// Get Entry from UUID // Get Entry from UUID
try { try {
val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY) val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY)
@@ -359,7 +371,6 @@ class EntryActivity : LockingActivity() {
taColorAccent.recycle() taColorAccent.recycle()
} }
val entryHistory = entry.getHistory() val entryHistory = entry.getHistory()
// TODO isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty() val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView) entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) { if (showHistoryView) {
@@ -462,8 +473,7 @@ class EntryActivity : LockingActivity() {
getString(R.string.entry_user_name))) getString(R.string.entry_user_name)))
}, },
{ {
// Launch autofill settings performedNextEducation(entryActivityEducation, menu)
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
}) })
if (!entryCopyEducationPerformed) { if (!entryCopyEducationPerformed) {
@@ -474,9 +484,7 @@ class EntryActivity : LockingActivity() {
onOptionsItemSelected(menu.findItem(R.id.menu_edit)) onOptionsItemSelected(menu.findItem(R.id.menu_edit))
}, },
{ {
// Open Keepass doc to create field references performedNextEducation(entryActivityEducation, menu)
startActivity(Intent(Intent.ACTION_VIEW,
UriUtil.parse(getString(R.string.field_references_url))))
}) })
} }
} }
@@ -526,10 +534,6 @@ class EntryActivity : LockingActivity() {
!mReadOnly && mAutoSaveEnable) !mReadOnly && mAutoSaveEnable)
} }
} }
R.id.menu_lock -> {
lockAndExit()
return true
}
R.id.menu_save_database -> { R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly) mProgressDialogThread?.startDatabaseSave(!mReadOnly)
} }

View File

@@ -81,6 +81,7 @@ class EntryEditActivity : LockingActivity(),
private var entryEditContentsView: EntryEditContentsView? = null private var entryEditContentsView: EntryEditContentsView? = null
private var entryEditAddToolBar: ActionMenuView? = null private var entryEditAddToolBar: ActionMenuView? = null
private var saveView: View? = null private var saveView: View? = null
private var lockView: View? = null
// Education // Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null private var entryEditActivityEducation: EntryEditActivityEducation? = null
@@ -112,6 +113,12 @@ class EntryEditActivity : LockingActivity(),
.show(supportFragmentManager, "DatePickerFragment") .show(supportFragmentManager, "DatePickerFragment")
} }
} }
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView) resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
@@ -241,6 +248,16 @@ class EntryEditActivity : LockingActivity(),
} }
} }
override fun onResume() {
super.onResume()
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
}
private fun populateViewsWithEntry(newEntry: Entry) { private fun populateViewsWithEntry(newEntry: Entry) {
// Don't start the field reference manager, we want to see the raw ref // Don't start the field reference manager, we want to see the raw ref
mDatabase?.stopManageEntry(newEntry) mDatabase?.stopManageEntry(newEntry)
@@ -251,7 +268,10 @@ class EntryEditActivity : LockingActivity(),
// Set info in view // Set info in view
entryEditContentsView?.apply { entryEditContentsView?.apply {
title = newEntry.title title = newEntry.title
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username username = if (mIsNew && newEntry.username.isEmpty())
mDatabase?.defaultUsername ?: ""
else
newEntry.username
url = newEntry.url url = newEntry.url
password = newEntry.password password = newEntry.password
expires = newEntry.expires expires = newEntry.expires
@@ -416,10 +436,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_lock -> {
lockAndExit()
return true
}
R.id.menu_save_database -> { R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly) mProgressDialogThread?.startDatabaseSave(!mReadOnly)
} }

View File

@@ -26,13 +26,11 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.os.Handler import android.os.Handler
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.TextView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
@@ -48,9 +46,11 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
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.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.*
@@ -63,7 +63,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
// Views // Views
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var fileListContainer: View? = null private var fileManagerExplanationButton: View? = null
private var createButtonView: View? = null private var createButtonView: View? = null
private var openDatabaseButtonView: View? = null private var openDatabaseButtonView: View? = null
@@ -85,12 +85,16 @@ class FileDatabaseSelectActivity : StylishActivity(),
setContentView(R.layout.activity_file_selection) setContentView(R.layout.activity_file_selection)
coordinatorLayout = findViewById(R.id.activity_file_selection_coordinator_layout) coordinatorLayout = findViewById(R.id.activity_file_selection_coordinator_layout)
fileListContainer = findViewById(R.id.container_file_list)
val toolbar = findViewById<Toolbar>(R.id.toolbar) val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = "" toolbar.title = ""
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
fileManagerExplanationButton = findViewById(R.id.file_manager_explanation_button)
fileManagerExplanationButton?.setOnClickListener {
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
}
// Create button // Create button
createButtonView = findViewById(R.id.create_database_button) createButtonView = findViewById(R.id.create_database_button)
if (allowCreateDocumentByStorageAccessFramework(packageManager)) { if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
@@ -105,8 +109,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
createButtonView?.setOnClickListener { createNewFile() } createButtonView?.setOnClickListener { createNewFile() }
mOpenFileHelper = OpenFileHelper(this) mOpenFileHelper = OpenFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_database_button) openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener) openDatabaseButtonView?.apply {
mOpenFileHelper?.openFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
// History list // History list
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list) val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
@@ -121,7 +130,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
databaseFileUri, databaseFileUri,
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri)) UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
} }
updateFileListVisibility()
} }
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete -> mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
// Remove from app database // Remove from app database
@@ -130,7 +138,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
fileHistoryDeleted?.let { databaseFileHistoryDeleted -> fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted) mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
mAdapterDatabaseHistory?.notifyDataSetChanged() mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
} }
} }
true true
@@ -212,7 +219,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
try { try {
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity, PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
databaseUri, keyFile, databaseUri, keyFile,
assistStructure) assistStructure,
intent.getParcelableExtra(KEY_SEARCH_INFO))
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
fileNoFoundAction(e) fileNoFoundAction(e)
} }
@@ -224,16 +232,21 @@ class FileDatabaseSelectActivity : StylishActivity(),
private fun launchGroupActivity(readOnly: Boolean) { private fun launchGroupActivity(readOnly: Boolean) {
EntrySelectionHelper.doEntrySelectionAction(intent, EntrySelectionHelper.doEntrySelectionAction(intent,
{ {
GroupActivity.launch(this@FileDatabaseSelectActivity, readOnly) GroupActivity.launch(this@FileDatabaseSelectActivity,
readOnly)
}, },
{ {
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity, readOnly) GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity,
readOnly)
// Do not keep history // Do not keep history
finish() finish()
}, },
{ assistStructure -> { assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity, assistStructure, readOnly) GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
assistStructure,
intent.getParcelableExtra(KEY_SEARCH_INFO),
readOnly)
} }
}) })
} }
@@ -245,25 +258,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
overridePendingTransition(0, 0) overridePendingTransition(0, 0)
} }
private fun updateExternalStorageWarning() {
// To show errors
var warning = -1
val state = Environment.getExternalStorageState()
if (state == Environment.MEDIA_MOUNTED_READ_ONLY) {
warning = R.string.read_only_warning
} else if (state != Environment.MEDIA_MOUNTED) {
warning = R.string.warning_unmounted
}
val labelWarningView = findViewById<TextView>(R.id.label_warning)
if (warning != -1) {
labelWarningView.setText(warning)
labelWarningView.visibility = View.VISIBLE
} else {
labelWarningView.visibility = View.INVISIBLE
}
}
override fun onResume() { override fun onResume() {
val database = Database.getInstance() val database = Database.getInstance()
if (database.loaded) { if (database.loaded) {
@@ -272,8 +266,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
super.onResume() super.onResume()
updateExternalStorageWarning()
// Construct adapter with listeners // Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this)) { if (PreferencesUtil.showRecentFiles(this)) {
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList -> mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
@@ -289,13 +281,11 @@ class FileDatabaseSelectActivity : StylishActivity(),
true true
}) })
mAdapterDatabaseHistory?.notifyDataSetChanged() mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
} }
} }
} else { } else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList() mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged() mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
} }
// Register progress task // Register progress task
@@ -317,13 +307,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri) outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
} }
private fun updateFileListVisibility() {
if (mAdapterDatabaseHistory?.itemCount == 0)
fileListContainer?.visibility = View.INVISIBLE
else
fileListContainer?.visibility = View.VISIBLE
}
override fun onAssignKeyDialogPositiveClick( override fun onAssignKeyDialogPositiveClick(
masterPasswordChecked: Boolean, masterPassword: String?, masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?) { keyFileChecked: Boolean, keyFile: Uri?) {
@@ -454,10 +437,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
*/ */
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure) { fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure,
searchInfo: SearchInfo?) {
AutofillHelper.startActivityForAutofillResult(activity, AutofillHelper.startActivityForAutofillResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java), Intent(activity, FileDatabaseSelectActivity::class.java),
assistStructure) assistStructure,
searchInfo)
} }
} }
} }

View File

@@ -62,6 +62,7 @@ import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
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
@@ -89,6 +90,7 @@ class GroupActivity : LockingActivity(),
// Views // Views
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
private var searchTitleView: View? = null private var searchTitleView: View? = null
private var toolbarAction: ToolbarAction? = null private var toolbarAction: ToolbarAction? = null
@@ -134,6 +136,11 @@ class GroupActivity : LockingActivity(),
groupNameView = findViewById(R.id.group_name) groupNameView = findViewById(R.id.group_name)
toolbarAction = findViewById(R.id.toolbar_action) toolbarAction = findViewById(R.id.toolbar_action)
modeTitleView = findViewById(R.id.mode_title_view) modeTitleView = findViewById(R.id.mode_title_view)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
toolbar?.title = "" toolbar?.title = ""
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
@@ -347,7 +354,7 @@ class GroupActivity : LockingActivity(),
// If it's a search // If it's a search
if (Intent.ACTION_SEARCH == intent.action) { if (Intent.ACTION_SEARCH == intent.action) {
val searchString = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: "" val searchString = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
return mDatabase?.search(searchString) return mDatabase?.createVirtualGroupFromSearch(searchString)
} }
// else a real group // else a real group
else { else {
@@ -486,7 +493,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.buildResponseWhenEntrySelected(this@GroupActivity, AutofillHelper.buildResponse(this@GroupActivity,
entryVersioned.getEntryInfo(database)) entryVersioned.getEntryInfo(database))
} }
} }
@@ -632,6 +639,13 @@ class GroupActivity : LockingActivity(),
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// Show the lock button
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
// Refresh the elements // Refresh the elements
assignGroupViewElements() assignGroupViewElements()
// Refresh suggestions to change preferences // Refresh suggestions to change preferences
@@ -753,12 +767,11 @@ class GroupActivity : LockingActivity(),
if (!sortMenuEducationPerformed) { if (!sortMenuEducationPerformed) {
// lockMenuEducationPerformed // lockMenuEducationPerformed
toolbar != null val lockButtonView = findViewById<View>(R.id.lock_button_icon)
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null lockButtonView != null
&& groupActivityEducation.checkAndPerformedLockMenuEducation( && groupActivityEducation.checkAndPerformedLockMenuEducation(lockButtonView,
toolbar!!.findViewById(R.id.menu_lock),
{ {
onOptionsItemSelected(menu.findItem(R.id.menu_lock)) lockAndExit()
}, },
{ {
performedNextEducation(groupActivityEducation, menu) performedNextEducation(groupActivityEducation, menu)
@@ -777,10 +790,6 @@ class GroupActivity : LockingActivity(),
R.id.menu_search -> R.id.menu_search ->
//onSearchRequested(); //onSearchRequested();
return true return true
R.id.menu_lock -> {
lockAndExit()
return true
}
R.id.menu_save_database -> { R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly) mProgressDialogThread?.startDatabaseSave(!mReadOnly)
return true return true
@@ -834,7 +843,7 @@ class GroupActivity : LockingActivity(),
removeChildren() removeChildren()
title = name title = name
this.icon = icon // TODO custom icon this.icon = icon // TODO custom icon #96
} }
} }
// If group updated save it in the database // If group updated save it in the database
@@ -956,27 +965,41 @@ class GroupActivity : LockingActivity(),
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG" private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY" private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
private fun buildIntent(context: Context, group: Group?, readOnly: Boolean, private fun buildIntent(context: Context,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) { intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(context, GroupActivity::class.java) val intent = Intent(context, GroupActivity::class.java)
if (group != null) { if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.nodeId) intent.putExtra(GROUP_ID_KEY, group.nodeId)
} }
if (searchInfo != null) {
intent.action = Intent.ACTION_SEARCH
val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId
intent.putExtra(SearchManager.QUERY, searchQuery)
}
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly) ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intentBuildLauncher.invoke(intent) intentBuildLauncher.invoke(intent)
} }
private fun checkTimeAndBuildIntent(activity: Activity, group: Group?, readOnly: Boolean, private fun checkTimeAndBuildIntent(activity: Activity,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) { intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
buildIntent(activity, group, readOnly, intentBuildLauncher) buildIntent(activity, group, searchInfo, readOnly, intentBuildLauncher)
} }
} }
private fun checkTimeAndBuildIntent(context: Context, group: Group?, readOnly: Boolean, private fun checkTimeAndBuildIntent(context: Context,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) { intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTime(context)) { if (TimeoutHelper.checkTime(context)) {
buildIntent(context, group, readOnly, intentBuildLauncher) buildIntent(context, group, searchInfo, readOnly, intentBuildLauncher)
} }
} }
@@ -985,11 +1008,9 @@ class GroupActivity : LockingActivity(),
* Standard Launch * Standard Launch
* ------------------------- * -------------------------
*/ */
@JvmOverloads
fun launch(context: Context, fun launch(context: Context,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) { readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
checkTimeAndBuildIntent(context, null, readOnly) { intent -> checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
context.startActivity(intent) context.startActivity(intent)
} }
} }
@@ -999,11 +1020,10 @@ class GroupActivity : LockingActivity(),
* Keyboard Launch * Keyboard Launch
* ------------------------- * -------------------------
*/ */
// TODO implement pre search to directly open the direct group // TODO implement pre search to directly open the direct group #280
fun launchForKeyboardSelection(context: Context, fun launchForKeyboardSelection(context: Context,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) { readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
checkTimeAndBuildIntent(context, null, readOnly) { intent -> checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
EntrySelectionHelper.startActivityForEntrySelection(context, intent) EntrySelectionHelper.startActivityForEntrySelection(context, intent)
} }
} }
@@ -1013,14 +1033,13 @@ class GroupActivity : LockingActivity(),
* Autofill Launch * Autofill Launch
* ------------------------- * -------------------------
*/ */
// TODO implement pre search to directly open the direct group
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity, fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure, assistStructure: AssistStructure,
searchInfo: SearchInfo? = null,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) { readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
checkTimeAndBuildIntent(activity, null, readOnly) { intent -> checkTimeAndBuildIntent(activity, null, searchInfo, readOnly) { intent ->
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure) AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure, searchInfo)
} }
} }
} }

View File

@@ -303,7 +303,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
if (readOnly if (readOnly
|| isASearchResult || isASearchResult
|| nodes.any { it.type == Type.GROUP }) { || nodes.any { it.type == Type.GROUP }) {
// TODO COPY For Group // TODO Copy For Group
menu?.removeItem(R.id.menu_copy) menu?.removeItem(R.id.menu_copy)
menu?.removeItem(R.id.menu_move) menu?.removeItem(R.id.menu_move)
} }

View File

@@ -23,6 +23,7 @@ import android.app.Activity
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -32,13 +33,11 @@ import android.text.TextWatcher
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.Button import android.widget.*
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat
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
@@ -50,11 +49,13 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.action.ProgressDialogThread
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
import com.kunzisoft.keepass.education.PasswordActivityEducation import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.model.SearchInfo
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.CIPHER_ENTITY_KEY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
@@ -66,6 +67,7 @@ import com.kunzisoft.keepass.utils.FileDatabaseInfo
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.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_password.* import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException import java.io.FileNotFoundException
@@ -77,7 +79,7 @@ open class PasswordActivity : StylishActivity() {
private var containerView: View? = null private var containerView: View? = null
private var filenameView: TextView? = null private var filenameView: TextView? = null
private var passwordView: EditText? = null private var passwordView: EditText? = null
private var keyFileView: EditText? = null private var keyFileSelectionView: KeyFileSelectionView? = null
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
@@ -92,6 +94,7 @@ open class PasswordActivity : StylishActivity() {
private var mRememberKeyFile: Boolean = false private var mRememberKeyFile: Boolean = false
private var mOpenFileHelper: OpenFileHelper? = null private var mOpenFileHelper: OpenFileHelper? = null
private var mPermissionAsked = false
private var readOnly: Boolean = false private var readOnly: Boolean = false
private var mForceReadOnly: Boolean = false private var mForceReadOnly: Boolean = false
set(value) { set(value) {
@@ -123,18 +126,23 @@ open class PasswordActivity : StylishActivity() {
confirmButtonView = findViewById(R.id.activity_password_open_button) confirmButtonView = findViewById(R.id.activity_password_open_button)
filenameView = findViewById(R.id.filename) filenameView = findViewById(R.id.filename)
passwordView = findViewById(R.id.password) passwordView = findViewById(R.id.password)
keyFileView = findViewById(R.id.pass_keyfile) 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)
checkboxDefaultDatabaseView = findViewById(R.id.default_database) checkboxDefaultDatabaseView = findViewById(R.id.default_database)
advancedUnlockInfoView = findViewById(R.id.biometric_info) 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
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
val browseView = findViewById<View>(R.id.open_database_button)
mOpenFileHelper = OpenFileHelper(this@PasswordActivity) mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener) keyFileSelectionView?.apply {
mOpenFileHelper?.openFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
passwordView?.setOnEditorActionListener(onEditorActionListener) passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher { passwordView?.addTextChangedListener(object : TextWatcher {
@@ -147,17 +155,6 @@ open class PasswordActivity : StylishActivity() {
checkboxPasswordView?.isChecked = true checkboxPasswordView?.isChecked = true
} }
}) })
keyFileView?.setOnEditorActionListener(onEditorActionListener)
keyFileView?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
if (editable.toString().isNotEmpty() && checkboxKeyFileView?.isChecked != true)
checkboxKeyFileView?.isChecked = true
}
})
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ -> enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
enableOrNotTheConfirmationButton() enableOrNotTheConfirmationButton()
@@ -260,16 +257,38 @@ open class PasswordActivity : StylishActivity() {
private fun launchGroupActivity() { private fun launchGroupActivity() {
EntrySelectionHelper.doEntrySelectionAction(intent, EntrySelectionHelper.doEntrySelectionAction(intent,
{ {
GroupActivity.launch(this@PasswordActivity, readOnly) GroupActivity.launch(this@PasswordActivity,
readOnly)
}, },
{ {
GroupActivity.launchForKeyboardSelection(this@PasswordActivity, readOnly) GroupActivity.launchForKeyboardSelection(this@PasswordActivity,
readOnly)
// Do not keep history // Do not keep history
finish() finish()
}, },
{ assistStructure -> { assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly) val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
AutofillHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
// Response is build
AutofillHelper.buildResponse(this, items)
finish()
},
{
// Here no search info found
GroupActivity.launchForAutofillResult(this@PasswordActivity,
assistStructure,
null,
readOnly)
},
{
// Simply close if database not opened, normally not happened
finish()
}
)
} }
}) })
} }
@@ -285,11 +304,12 @@ open class PasswordActivity : StylishActivity() {
} }
override fun onResume() { override fun onResume() {
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
if (Database.getInstance().loaded) if (Database.getInstance().loaded)
launchGroupActivity() launchGroupActivity()
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
// If the database isn't accessible make sure to clear the password field, if it // If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state // was saved in the instance state
if (Database.getInstance().loaded) { if (Database.getInstance().loaded) {
@@ -302,9 +322,12 @@ open class PasswordActivity : StylishActivity() {
mProgressDialogThread?.registerProgressTask() mProgressDialogThread?.registerProgressTask()
initUriFromIntent() initUriFromIntent()
checkPermission()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
mDatabaseKeyFileUri?.let { mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString()) outState.putString(KEY_KEYFILE, it.toString())
} }
@@ -319,7 +342,9 @@ open class PasswordActivity : StylishActivity() {
!FileDatabaseInfo(this, it).canWrite !FileDatabaseInfo(this, it).canWrite
} ?: false } ?: false
*/ */
mForceReadOnly = false mForceReadOnly = mDatabaseFileUri?.let {
!FileDatabaseInfo(this, it).exists
} ?: true
// Post init uri with KeyFile if needed // Post init uri with KeyFile if needed
if (mRememberKeyFile && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) { if (mRememberKeyFile && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
@@ -345,7 +370,7 @@ open class PasswordActivity : StylishActivity() {
// Define Key File text // Define Key File text
if (mRememberKeyFile) { if (mRememberKeyFile) {
populateKeyFileTextView(keyFileUri?.toString()) populateKeyFileTextView(keyFileUri)
} }
// Define listeners for default database checkbox and validate button // Define listeners for default database checkbox and validate button
@@ -459,13 +484,13 @@ open class PasswordActivity : StylishActivity() {
} }
} }
private fun populateKeyFileTextView(text: String?) { private fun populateKeyFileTextView(uri: Uri?) {
if (text == null || text.isEmpty()) { if (uri == null || uri.toString().isEmpty()) {
keyFileView?.setText("") keyFileSelectionView?.uri = null
if (checkboxKeyFileView?.isChecked == true) if (checkboxKeyFileView?.isChecked == true)
checkboxKeyFileView?.isChecked = false checkboxKeyFileView?.isChecked = false
} else { } else {
keyFileView?.setText(text) keyFileSelectionView?.uri = uri
if (checkboxKeyFileView?.isChecked != true) if (checkboxKeyFileView?.isChecked != true)
checkboxKeyFileView?.isChecked = true checkboxKeyFileView?.isChecked = true
} }
@@ -486,7 +511,7 @@ open class PasswordActivity : StylishActivity() {
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) { private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
val password: String? = passwordView?.text?.toString() val password: String? = passwordView?.text?.toString()
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString()) val keyFile: Uri? = keyFileSelectionView?.uri
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity) verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
} }
@@ -499,7 +524,7 @@ open class PasswordActivity : StylishActivity() {
} }
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) { private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString()) val keyFile: Uri? = keyFileSelectionView?.uri
verifyKeyFileCheckbox(keyFile) verifyKeyFileCheckbox(keyFile)
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri) loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
} }
@@ -576,28 +601,56 @@ open class PasswordActivity : StylishActivity() {
return true return true
} }
// Check permission
private fun checkPermission() {
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissions = arrayOf(writePermission)
if (Build.VERSION.SDK_INT >= 23
&& !readOnly
&& !mPermissionAsked) {
mPermissionAsked = true
// Check self permission to show or not the dialog
if (toolbar != null
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
WRITE_EXTERNAL_STORAGE_REQUEST -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
Toast.makeText(this, R.string.read_only_warning, Toast.LENGTH_LONG).show()
}
}
}
}
// To fix multiple view education // To fix multiple view education
private var performedEductionInProgress = false private var performedEductionInProgress = false
private fun launchEducation(menu: Menu, onEducationFinished: (()-> Unit)? = null) { private fun launchEducation(menu: Menu) {
if (!performedEductionInProgress) { if (!performedEductionInProgress) {
performedEductionInProgress = true performedEductionInProgress = true
// Show education views // Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) } Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
} }
} }
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation, private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu, menu: Menu) {
onEducationFinished: (()-> Unit)? = null) {
val educationToolbar = toolbar val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation( && passwordActivityEducation.checkAndPerformedUnlockEducation(
educationToolbar, educationToolbar,
{ {
performedNextEducation(passwordActivityEducation, menu, onEducationFinished) performedNextEducation(passwordActivityEducation, menu)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu, onEducationFinished) performedNextEducation(passwordActivityEducation, menu)
}) })
if (!unlockEducationPerformed) { if (!unlockEducationPerformed) {
val readOnlyEducationPerformed = val readOnlyEducationPerformed =
@@ -606,30 +659,25 @@ open class PasswordActivity : StylishActivity() {
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key), educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
{ {
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key)) onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
performedNextEducation(passwordActivityEducation, menu, onEducationFinished) performedNextEducation(passwordActivityEducation, menu)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu, onEducationFinished) performedNextEducation(passwordActivityEducation, menu)
}) })
if (!readOnlyEducationPerformed) { if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate() val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
val biometricEducationPerformed =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext) && PreferencesUtil.isBiometricUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null && advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!, && passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{ {
performedNextEducation(passwordActivityEducation, menu, onEducationFinished) performedNextEducation(passwordActivityEducation, menu)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu, onEducationFinished) performedNextEducation(passwordActivityEducation, menu)
}) })
if (!biometricEducationPerformed) {
onEducationFinished?.invoke()
}
} }
} }
} }
@@ -678,7 +726,7 @@ open class PasswordActivity : StylishActivity() {
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
mDatabaseKeyFileUri = uri mDatabaseKeyFileUri = uri
populateKeyFileTextView(uri.toString()) populateKeyFileTextView(uri)
} }
} }
} }
@@ -703,6 +751,8 @@ open class PasswordActivity : StylishActivity() {
private const val KEY_PASSWORD = "password" private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately" private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?, private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) { intentBuildLauncher: (Intent) -> Unit) {
@@ -757,13 +807,15 @@ open class PasswordActivity : StylishActivity() {
activity: Activity, activity: Activity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?, keyFile: Uri?,
assistStructure: AssistStructure?) { assistStructure: AssistStructure?,
searchInfo: SearchInfo?) {
if (assistStructure != null) { if (assistStructure != null) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
AutofillHelper.startActivityForAutofillResult( AutofillHelper.startActivityForAutofillResult(
activity, activity,
intent, intent,
assistStructure) assistStructure,
searchInfo)
} }
} else { } else {
launch(activity, databaseFile, keyFile) launch(activity, databaseFile, keyFile)

View File

@@ -35,7 +35,7 @@ import android.widget.CompoundButton
import android.widget.TextView import android.widget.TextView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.view.KeyFileSelectionView
class AssignMasterKeyDialogFragment : DialogFragment() { class AssignMasterKeyDialogFragment : DialogFragment() {
@@ -51,9 +51,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var passwordRepeatTextInputLayout: TextInputLayout? = null private var passwordRepeatTextInputLayout: TextInputLayout? = null
private var passwordRepeatView: TextView? = null private var passwordRepeatView: TextView? = null
private var keyFileTextInputLayout: TextInputLayout? = null
private var keyFileCheckBox: CompoundButton? = null private var keyFileCheckBox: CompoundButton? = null
private var keyFileView: TextView? = null private var keyFileSelectionView: KeyFileSelectionView? = null
private var mListener: AssignPasswordDialogListener? = null private var mListener: AssignPasswordDialogListener? = null
@@ -69,16 +68,6 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
} }
} }
private val keyFileTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
keyFileCheckBox?.isChecked = true
}
}
interface AssignPasswordDialogListener { interface AssignPasswordDialogListener {
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?, fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?) keyFileChecked: Boolean, keyFile: Uri?)
@@ -121,13 +110,14 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout) passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password) passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
keyFileTextInputLayout = rootView?.findViewById(R.id.keyfile_input_layout)
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox) keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileView = rootView?.findViewById(R.id.pass_keyfile) keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
mOpenFileHelper = OpenFileHelper(this) mOpenFileHelper = OpenFileHelper(this)
rootView?.findViewById<View>(R.id.open_database_button)?.setOnClickListener { view -> keyFileSelectionView?.apply {
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) } setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener)
}
val dialog = builder.create() val dialog = builder.create()
@@ -176,14 +166,12 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
// To check checkboxes if a text is present // To check checkboxes if a text is present
passwordView?.addTextChangedListener(passwordTextWatcher) passwordView?.addTextChangedListener(passwordTextWatcher)
keyFileView?.addTextChangedListener(keyFileTextWatcher)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
passwordView?.removeTextChangedListener(passwordTextWatcher) passwordView?.removeTextChangedListener(passwordTextWatcher)
keyFileView?.removeTextChangedListener(keyFileTextWatcher)
} }
private fun verifyPassword(): Boolean { private fun verifyPassword(): Boolean {
@@ -216,11 +204,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
if (keyFileCheckBox != null if (keyFileCheckBox != null
&& keyFileCheckBox!!.isChecked) { && keyFileCheckBox!!.isChecked) {
UriUtil.parse(keyFileView?.text?.toString())?.let { uri -> keyFileSelectionView?.uri?.let { uri ->
mKeyFile = uri mKeyFile = uri
} ?: run { } ?: run {
error = true error = true
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile) keyFileSelectionView?.error = getString(R.string.error_nokeyfile)
} }
} }
return error return error
@@ -265,8 +253,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
) { uri -> ) { uri ->
uri?.let { pathUri -> uri?.let { pathUri ->
keyFileCheckBox?.isChecked = true keyFileCheckBox?.isChecked = true
keyFileView?.text = pathUri.toString() keyFileSelectionView?.uri = pathUri
} }
} }
} }

View File

@@ -28,7 +28,7 @@ import android.widget.TextView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
class BrowserDialogFragment : DialogFragment() { class FileManagerDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
@@ -41,15 +41,8 @@ class BrowserDialogFragment : DialogFragment() {
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description) val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
textDescription.text = getString(R.string.file_manager_install_description) textDescription.text = getString(R.string.file_manager_install_description)
val market = root.findViewById<Button>(R.id.file_manager_install_play_store) root.findViewById<Button>(R.id.file_manager_button).setOnClickListener {
market.setOnClickListener { UriUtil.gotoUrl(requireContext(), R.string.file_manager_explanation_url)
UriUtil.gotoUrl(context!!, R.string.file_manager_play_store)
dismiss()
}
val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
web.setOnClickListener {
UriUtil.gotoUrl(context!!, R.string.file_manager_f_droid)
dismiss() dismiss()
} }

View File

@@ -167,7 +167,6 @@ class GeneratePasswordDialogFragment : DialogFragment() {
var password = "" var password = ""
try { try {
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString()) val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
password = PasswordGenerator(resources).generatePassword(length, password = PasswordGenerator(resources).generatePassword(length,
uppercaseBox?.isChecked == true, uppercaseBox?.isChecked == true,
lowercaseBox?.isChecked == true, lowercaseBox?.isChecked == true,
@@ -178,6 +177,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
specialsBox?.isChecked == true, specialsBox?.isChecked == true,
bracketsBox?.isChecked == true, bracketsBox?.isChecked == true,
extendedBox?.isChecked == true) extendedBox?.isChecked == true)
passwordInputLayoutView?.error = null
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
passwordInputLayoutView?.error = getString(R.string.error_wrong_length) passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
@@ -193,7 +193,6 @@ class GeneratePasswordDialogFragment : DialogFragment() {
} }
companion object { companion object {
const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID" const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID"
} }
} }

View File

@@ -130,9 +130,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
} }
iconButtonView?.setOnClickListener { _ -> iconButtonView?.setOnClickListener { _ ->
fragmentManager?.let { IconPickerDialogFragment().show(parentFragmentManager, "IconPickerDialogFragment")
IconPickerDialogFragment().show(it, "IconPickerDialogFragment")
}
} }
return builder.create() return builder.create()

View File

@@ -45,13 +45,13 @@ class ProFeatureDialogFragment : DialogFragment() {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY)) stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ -> builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.app_pro_url) UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
} }
} else { } else {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY)) stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.contribution_url) UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
} }
} }
builder.setMessage(stringBuilder) builder.setMessage(stringBuilder)

View File

@@ -52,7 +52,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY)) .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ -> builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.app_pro_url) UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
} }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() } builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
} }
@@ -61,7 +61,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY)) .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.contribution_url) UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
} }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() } builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
} }

View File

@@ -31,7 +31,7 @@ import android.util.Log
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
class OpenFileHelper { class OpenFileHelper {
@@ -52,14 +52,22 @@ class OpenFileHelper {
this.fragment = context this.fragment = context
} }
inner class OpenFileOnClickViewListener : View.OnClickListener { inner class OpenFileOnClickViewListener : View.OnClickListener, View.OnLongClickListener {
override fun onClick(v: View) { private fun onAbstractClick(longClick: Boolean = false) {
try { try {
try { if (longClick) {
openActivityWithActionOpenDocument() try {
} catch(e: Exception) { openActivityWithActionGetContent()
openActivityWithActionGetContent() } catch (e: Exception) {
openActivityWithActionOpenDocument()
}
} else {
try {
openActivityWithActionOpenDocument()
} catch (e: Exception) {
openActivityWithActionGetContent()
}
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Enable to start the file picker activity", e) Log.e(TAG, "Enable to start the file picker activity", e)
@@ -68,6 +76,15 @@ class OpenFileHelper {
showBrowserDialog() showBrowserDialog()
} }
} }
override fun onClick(v: View) {
onAbstractClick()
}
override fun onLongClick(v: View?): Boolean {
onAbstractClick(true)
return true
}
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
@@ -147,11 +164,10 @@ class OpenFileHelper {
*/ */
private fun showBrowserDialog() { private fun showBrowserDialog() {
try { try {
val browserDialogFragment = BrowserDialogFragment() val fileManagerDialogFragment = FileManagerDialogFragment()
if (fragment != null && fragment!!.fragmentManager != null) fragment?.let {
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog") fileManagerDialogFragment.show(it.parentFragmentManager, "browserDialog")
else } ?: fileManagerDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e) Log.e(TAG, "Can't open BrowserDialog", e)
} }

View File

@@ -19,7 +19,6 @@
*/ */
package com.kunzisoft.keepass.activities.lock package com.kunzisoft.keepass.activities.lock
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@@ -75,7 +74,10 @@ abstract class LockingActivity : StylishActivity() {
if (mTimeoutEnable) { if (mTimeoutEnable) {
mLockReceiver = LockReceiver { mLockReceiver = LockReceiver {
lockAndExit() closeDatabase()
// Add onActivityForResult response
setResult(RESULT_EXIT_LOCK)
finish()
} }
registerLockReceiver(mLockReceiver) registerLockReceiver(mLockReceiver)
} }
@@ -148,7 +150,6 @@ abstract class LockingActivity : StylishActivity() {
protected fun lockAndExit() { protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION)) sendBroadcast(Intent(LOCK_ACTION))
lock()
} }
/** /**
@@ -180,11 +181,3 @@ abstract class LockingActivity : StylishActivity() {
} }
} }
} }
fun Activity.lock() {
closeDatabase()
// Add onActivityForResult response
setResult(LockingActivity.RESULT_EXIT_LOCK)
finish()
}

View File

@@ -41,8 +41,9 @@ abstract class StylishActivity : AppCompatActivity() {
*/ */
override fun startActivity(intent: Intent) { override fun startActivity(intent: Intent) {
try { try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") { intent.component?.let {
intent.component = null if (it.shortClassName == ".HtcLinkifyDispatcherActivity")
intent.component = null
} }
super.startActivity(intent) super.startActivity(intent)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
@@ -52,19 +53,19 @@ abstract class StylishActivity : AppCompatActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.themeId = Stylish.getThemeId(this) this.themeId = Stylish.getThemeId(this)
setTheme(themeId) setTheme(themeId)
super.onCreate(savedInstanceState)
// Several gingerbread devices have problems with FLAG_SECURE // Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
} }
override fun onResume() { override fun onResume() {
super.onResume()
if (Stylish.getThemeId(this) != this.themeId) { if (Stylish.getThemeId(this) != this.themeId) {
Log.d(this.javaClass.name, "Theme change detected, restarting activity") Log.d(this.javaClass.name, "Theme change detected, restarting activity")
this.recreate() this.recreate()
} }
super.onResume()
} }
} }

View File

@@ -44,8 +44,8 @@ abstract class StylishFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// To fix status bar color // To fix status bar color
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = activity!!.window val window = requireActivity().window
val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark) val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark)
val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark) val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark)

View File

@@ -28,8 +28,14 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.cursor.EntryCursorKDB
import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX
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
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.strikeOut import com.kunzisoft.keepass.view.strikeOut
@@ -69,8 +75,7 @@ class SearchEntryCursorAdapter(private val context: Context,
} }
override fun bindView(view: View, context: Context, cursor: Cursor) { override fun bindView(view: View, context: Context, cursor: Cursor) {
getEntryFrom(cursor)?.let { currentEntry ->
database.getEntryFrom(cursor)?.let { currentEntry ->
val viewHolder = view.tag as ViewHolder val viewHolder = view.tag as ViewHolder
// Assign image // Assign image
@@ -98,14 +103,46 @@ class SearchEntryCursorAdapter(private val context: Context,
} }
} }
private class ViewHolder { private fun getEntryFrom(cursor: Cursor): Entry? {
internal var imageViewIcon: ImageView? = null return database.createEntry()?.apply {
internal var textViewTitle: TextView? = null database.startManageEntry(this)
internal var textViewSubTitle: TextView? = null entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconFactory)
}
entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconFactory)
}
database.stopManageEntry(this)
}
} }
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? { override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return database.searchEntries(context, constraint.toString()) return searchEntries(context, constraint.toString())
}
private fun searchEntries(context: Context, query: String): Cursor? {
var cursorKDB: EntryCursorKDB? = null
var cursorKDBX: EntryCursorKDBX? = null
if (database.type == DatabaseKDB.TYPE)
cursorKDB = EntryCursorKDB()
if (database.type == DatabaseKDBX.TYPE)
cursorKDBX = EntryCursorKDBX()
val searchGroup = database.createVirtualGroupFromSearch(query, SearchHelper.MAX_SEARCH_ENTRY)
if (searchGroup != null) {
// Search in hide entries but not meta-stream
for (entry in searchGroup.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) {
entry.entryKDB?.let {
cursorKDB?.addEntry(it)
}
entry.entryKDBX?.let {
cursorKDBX?.addEntry(it)
}
}
}
return cursorKDB ?: cursorKDBX
} }
fun getEntryFromPosition(position: Int): Entry? { fun getEntryFromPosition(position: Int): Entry? {
@@ -113,9 +150,14 @@ class SearchEntryCursorAdapter(private val context: Context,
val cursor = this.cursor val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) { if (cursor.moveToFirst() && cursor.move(position)) {
pwEntry = database.getEntryFrom(cursor) pwEntry = getEntryFrom(cursor)
} }
return pwEntry return pwEntry
} }
private class ViewHolder {
internal var imageViewIcon: ImageView? = null
internal var textViewTitle: TextView? = null
internal var textViewSubTitle: TextView? = null
}
} }

View File

@@ -13,8 +13,6 @@ package com.kunzisoft.keepass.app;
import android.os.Build; import android.os.Build;
import android.os.Process; import android.os.Process;
import com.kunzisoft.keepass.utils.StringUtil;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
@@ -66,7 +64,7 @@ public final class PRNGFixes {
private static boolean supportedOnThisDevice() { private static boolean supportedOnThisDevice() {
// Blacklist on samsung devices // Blacklist on samsung devices
if (StringUtil.INSTANCE.indexOfIgnoreCase(Build.MANUFACTURER, "samsung", Locale.ENGLISH) >= 0) { if (Build.MANUFACTURER.toLowerCase(Locale.ENGLISH).contains("samsung")) {
return false; return false;
} }

View File

@@ -31,9 +31,17 @@ import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue import android.view.autofill.AutofillValue
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
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
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
@@ -42,6 +50,7 @@ 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 ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
fun retrieveAssistStructure(intent: Intent?): AssistStructure? { fun retrieveAssistStructure(intent: Intent?): AssistStructure? {
intent?.let { intent?.let {
@@ -62,11 +71,28 @@ object AutofillHelper {
return "" return ""
} }
private fun buildDataset(context: Context, internal fun addHeader(responseBuilder: FillResponse.Builder,
entryInfo: EntryInfo, packageName: String,
struct: StructureParser.Result): Dataset? { 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,
struct: StructureParser.Result): Dataset? {
val title = makeEntryTitle(entryInfo) val title = makeEntryTitle(entryInfo)
val views = newRemoteViews(context.packageName, title) val views = newRemoteViews(context, title, entryInfo.icon)
val builder = Dataset.Builder(views) val builder = Dataset.Builder(views)
builder.setId(entryInfo.id) builder.setId(entryInfo.id)
@@ -86,9 +112,16 @@ object AutofillHelper {
} }
/** /**
* Method to hit when right key is selected * Build the Autofill response for one entry
*/ */
fun buildResponseWhenEntrySelected(activity: Activity, entryInfo: EntryInfo) { fun buildResponse(activity: Activity, entryInfo: EntryInfo) {
buildResponse(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
}
/**
* Build the Autofill response for many entry
*/
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) {
var setResultOk = false var setResultOk = false
activity.intent?.extras?.let { extras -> activity.intent?.extras?.let { extras ->
if (extras.containsKey(ASSIST_STRUCTURE)) { if (extras.containsKey(ASSIST_STRUCTURE)) {
@@ -96,8 +129,9 @@ object AutofillHelper {
StructureParser(structure).parse()?.let { result -> StructureParser(structure).parse()?.let { result ->
// New Response // New Response
val responseBuilder = FillResponse.Builder() val responseBuilder = FillResponse.Builder()
val dataset = buildDataset(activity, entryInfo, result) entriesInfo.forEach {
responseBuilder.addDataset(dataset) responseBuilder.addDataset(buildDataset(activity, it, result))
}
val mReplyIntent = Intent() val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.") Log.d(activity.javaClass.name, "Successed Autofill auth.")
mReplyIntent.putExtra( mReplyIntent.putExtra(
@@ -115,12 +149,48 @@ object AutofillHelper {
} }
} }
/**
* Utility method to perform actions if item is found or not after an auto search in [database]
*/
fun checkAutoSearchInfo(context: Context,
database: Database,
searchInfo: SearchInfo?,
onItemsFound: (items: List<EntryInfo>) -> Unit,
onItemNotFound: () -> Unit,
onDatabaseClosed: () -> Unit) {
if (database.loaded && TimeoutHelper.checkTime(context)) {
var searchWithoutUI = false
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
&& searchInfo != null) {
// If search provide results
database.createVirtualGroupFromSearch(searchInfo, SearchHelper.MAX_SEARCH_ENTRY)?.let { searchGroup ->
if (searchGroup.getNumberOfChildEntries() > 0) {
searchWithoutUI = true
onItemsFound.invoke(
searchGroup.getChildEntriesInfo(database))
}
}
}
if (!searchWithoutUI) {
onItemNotFound.invoke()
}
} else {
onDatabaseClosed.invoke()
}
}
/** /**
* Utility method to start an activity with an Autofill for result * Utility method to start an activity with an Autofill for result
*/ */
fun startActivityForAutofillResult(activity: Activity, intent: Intent, assistStructure: AssistStructure) { fun startActivityForAutofillResult(activity: Activity,
intent: Intent,
assistStructure: AssistStructure,
searchInfo: SearchInfo?) {
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent) EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent)
intent.putExtra(ASSIST_STRUCTURE, assistStructure) intent.putExtra(ASSIST_STRUCTURE, assistStructure)
searchInfo?.let {
intent.putExtra(KEY_SEARCH_INFO, it)
}
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE) activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
} }
@@ -139,9 +209,18 @@ object AutofillHelper {
} }
} }
private fun newRemoteViews(packageName: String, remoteViewsText: String): RemoteViews { private fun newRemoteViews(context: Context,
val presentation = RemoteViews(packageName, R.layout.item_autofill_service) remoteViewsText: String,
presentation.setTextViewText(R.id.text, remoteViewsText) remoteViewsIcon: IconImage? = null): RemoteViews {
val presentation = RemoteViews(context.packageName, R.layout.item_autofill_entry)
presentation.setTextViewText(R.id.autofill_entry_text, remoteViewsText)
if (remoteViewsIcon != null) {
presentation.assignDatabaseIcon(context,
R.id.autofill_entry_icon,
Database.getInstance().drawFactory,
remoteViewsIcon,
ContextCompat.getColor(context, R.color.green))
}
return presentation return presentation
} }
} }

View File

@@ -31,7 +31,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.model.SearchInfo
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() { class AutofillLauncherActivity : AppCompatActivity() {
@@ -40,13 +40,31 @@ class AutofillLauncherActivity : AppCompatActivity() {
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE) // Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val assistStructure = AutofillHelper.retrieveAssistStructure(intent) val assistStructure = AutofillHelper.retrieveAssistStructure(intent)
if (assistStructure != null) { if (assistStructure != null) {
if (Database.getInstance().loaded && TimeoutHelper.checkTime(this)) // Build search param
GroupActivity.launchForAutofillResult(this, val searchInfo = SearchInfo().apply {
assistStructure) applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
else { webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
FileDatabaseSelectActivity.launchForAutofillResult(this,
assistStructure)
} }
// If database is open
AutofillHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
// Items found
AutofillHelper.buildResponse(this, items)
finish()
},
{
// Show the database UI to select the entry
GroupActivity.launchForAutofillResult(this,
assistStructure)
},
{
// If database not open
FileDatabaseSelectActivity.launchForAutofillResult(this,
assistStructure, searchInfo)
}
)
} else { } else {
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_CANCELED)
finish() finish()
@@ -62,10 +80,20 @@ class AutofillLauncherActivity : AppCompatActivity() {
companion object { companion object {
fun getAuthIntentSenderForResponse(context: Context): IntentSender { private const val KEY_SEARCH_APPLICATION_ID = "KEY_SEARCH_APPLICATION_ID"
val intent = Intent(context, AutofillLauncherActivity::class.java) private const val KEY_SEARCH_DOMAIN = "KEY_SEARCH_DOMAIN"
fun getAuthIntentSenderForResponse(context: Context,
searchInfo: SearchInfo? = null): IntentSender {
return PendingIntent.getActivity(context, 0, return PendingIntent.getActivity(context, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT).intentSender // Doesn't work with Parcelable (don't know why?)
Intent(context, AutofillLauncherActivity::class.java).apply {
searchInfo?.let {
putExtra(KEY_SEARCH_APPLICATION_ID, it.applicationId)
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
}
},
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
} }
} }
} }

View File

@@ -22,31 +22,78 @@ package com.kunzisoft.keepass.autofill
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 androidx.annotation.RequiresApi
import android.util.Log import android.util.Log
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.annotation.RequiresApi
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.SearchInfo
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class KeeAutofillService : AutofillService() { class KeeAutofillService : AutofillService() {
override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal, override fun onFillRequest(request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback) { callback: FillCallback) {
val fillContexts = request.fillContexts val fillContexts = request.fillContexts
val latestStructure = fillContexts[fillContexts.size - 1].structure val latestStructure = fillContexts[fillContexts.size - 1].structure
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") } cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
val responseBuilder = FillResponse.Builder()
// Check user's settings for authenticating Responses and Datasets. // Check user's settings for authenticating Responses and Datasets.
val parseResult = StructureParser(latestStructure).parse() StructureParser(latestStructure).parse()?.let { parseResult ->
parseResult?.allAutofillIds()?.let { autofillIds ->
val searchInfo = SearchInfo().apply {
applicationId = parseResult.applicationId
webDomain = parseResult.domain
}
AutofillHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
val responseBuilder = FillResponse.Builder()
AutofillHelper.addHeader(responseBuilder, packageName,
parseResult.domain, parseResult.applicationId)
items.forEach {
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
}
callback.onSuccess(responseBuilder.build())
},
{
// Show UI if no search result
showUIForEntrySelection(parseResult, searchInfo, callback)
},
{
// Show UI if database not open
showUIForEntrySelection(parseResult, searchInfo, callback)
}
)
}
}
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
searchInfo: SearchInfo,
callback: FillCallback) {
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 sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this) val sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this,
val presentation = RemoteViews(packageName, R.layout.item_autofill_service_unlock) searchInfo)
responseBuilder.setAuthentication(autofillIds, sender, presentation) val responseBuilder = FillResponse.Builder()
val remoteViewsUnlock: RemoteViews = if (!parseResult.domain.isNullOrEmpty()) {
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
setTextViewText(R.id.autofill_web_domain_text, parseResult.domain)
}
} else if (!parseResult.applicationId.isNullOrEmpty()) {
RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply {
setTextViewText(R.id.autofill_app_id_text, parseResult.applicationId)
}
} else {
RemoteViews(packageName, R.layout.item_autofill_unlock)
}
responseBuilder.setAuthentication(autofillIds, sender, remoteViewsUnlock)
callback.onSuccess(responseBuilder.build()) callback.onSuccess(responseBuilder.build())
} }
} }

View File

@@ -20,6 +20,7 @@ package com.kunzisoft.keepass.autofill
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.os.Build import android.os.Build
import android.text.InputType
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.util.Log import android.util.Log
import android.view.View import android.view.View
@@ -34,49 +35,63 @@ import java.util.*
internal class StructureParser(private val structure: AssistStructure) { internal class StructureParser(private val structure: AssistStructure) {
private var result: Result? = null private var result: Result? = null
private var usernameCandidate: AutofillId? = null private var usernameCandidate: AutofillId? = null
private var usernameNeeded = true
fun parse(): Result? { fun parse(): Result? {
result = Result() try {
result?.apply { result = Result()
usernameCandidate = null result?.apply {
mainLoop@ for (i in 0 until structure.windowNodeCount) { usernameCandidate = null
val windowNode = structure.getWindowNodeAt(i) mainLoop@ for (i in 0 until structure.windowNodeCount) {
/* val windowNode = structure.getWindowNodeAt(i)
title.add(windowNode.title) applicationId = windowNode.title.toString().split("/")[0]
windowNode.rootViewNode.webDomain?.let { Log.d(TAG, "Autofill applicationId: $applicationId")
webDomain.add(it)
}
*/
if (parseViewNode(windowNode.rootViewNode))
break@mainLoop
}
// If not explicit username field found, add the field just before password field.
if (usernameId == null && passwordId != null && usernameCandidate != null)
usernameId = usernameCandidate
}
// Return the result only if password field is retrieved if (parseViewNode(windowNode.rootViewNode))
return if (result?.passwordId != null) break@mainLoop
result }
else // If not explicit username field found, add the field just before password field.
null if (usernameId == null && passwordId != null && usernameCandidate != null)
usernameId = usernameCandidate
}
// Return the result only if password field is retrieved
return if ((!usernameNeeded || result?.usernameId != null)
&& result?.passwordId != null)
result
else
null
} catch (e: Exception) {
return null
}
} }
private fun parseViewNode(node: AssistStructure.ViewNode): Boolean { private fun parseViewNode(node: AssistStructure.ViewNode): Boolean {
if (node.autofillId != null) { // Get the domain of a web app
val hints = node.autofillHints node.webDomain?.let {
if (hints != null && hints.isNotEmpty()) { result?.domain = it
if (parseNodeByAutofillHint(node)) Log.d(TAG, "Autofill domain: $it")
}
// Only parse visible nodes
if (node.visibility == View.VISIBLE) {
if (node.autofillId != null
&& node.autofillType == View.AUTOFILL_TYPE_TEXT) {
// Parse methods
val hints = node.autofillHints
if (hints != null && hints.isNotEmpty()) {
if (parseNodeByAutofillHint(node))
return true
} else if (parseNodeByHtmlAttributes(node))
return true return true
} else { else if (parseNodeByAndroidInput(node))
if (parseNodeByHtmlAttributes(node)) return true
}
// Recursive method to process each node
for (i in 0 until node.childCount) {
if (parseViewNode(node.getChildAt(i)))
return true return true
} }
}
// Recursive method to process each node
for (i in 0 until node.childCount) {
if (parseViewNode(node.getChildAt(i)))
return true
} }
return false return false
} }
@@ -85,22 +100,26 @@ internal class StructureParser(private val structure: AssistStructure) {
val autofillId = node.autofillId val autofillId = node.autofillId
node.autofillHints?.forEach { node.autofillHints?.forEach {
when { when {
it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_USERNAME it.equals(View.AUTOFILL_HINT_USERNAME, true)
|| it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_EMAIL_ADDRESS || it.equals(View.AUTOFILL_HINT_EMAIL_ADDRESS, true)
|| it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_PHONE -> { || it.equals(View.AUTOFILL_HINT_PHONE, true)
|| it.equals("email", true)
|| it.equals("usernameOrEmail", true)-> {
result?.usernameId = autofillId result?.usernameId = autofillId
Log.d(TAG, "Autofill username hint") Log.d(TAG, "Autofill username hint")
} }
it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_PASSWORD it.equals(View.AUTOFILL_HINT_PASSWORD, true)
|| it.toLowerCase(Locale.ENGLISH).contains("password") -> { || it.contains("password", true) -> {
result?.passwordId = autofillId result?.passwordId = autofillId
Log.d(TAG, "Autofill password hint") Log.d(TAG, "Autofill password hint")
// Username not needed in this specific case
usernameNeeded = false
return true return true
} }
// Ignore autocomplete="off" // Ignore autocomplete="off"
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion // https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
it.toLowerCase(Locale.ENGLISH) == "off" || it.equals("off", true) ||
it.toLowerCase(Locale.ENGLISH) == "on" -> { it.equals("on", true) -> {
Log.d(TAG, "Autofill web hint") Log.d(TAG, "Autofill web hint")
return parseNodeByHtmlAttributes(node) return parseNodeByHtmlAttributes(node)
} }
@@ -121,15 +140,15 @@ internal class StructureParser(private val structure: AssistStructure) {
when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) { when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) {
"tel", "email" -> { "tel", "email" -> {
result?.usernameId = autofillId result?.usernameId = autofillId
Log.d(TAG, "Autofill username type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}") Log.d(TAG, "Autofill username web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
} }
"text" -> { "text" -> {
usernameCandidate = autofillId usernameCandidate = autofillId
Log.d(TAG, "Autofill type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}") Log.d(TAG, "Autofill username candidate web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
} }
"password" -> { "password" -> {
result?.passwordId = autofillId result?.passwordId = autofillId
Log.d(TAG, "Autofill password type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}") Log.d(TAG, "Autofill password web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
return true return true
} }
} }
@@ -141,8 +160,57 @@ internal class StructureParser(private val structure: AssistStructure) {
return false return false
} }
private fun parseNodeByAndroidInput(node: AssistStructure.ViewNode): Boolean {
val autofillId = node.autofillId
val inputType = node.inputType
if (inputType and InputType.TYPE_CLASS_TEXT != 0) {
when {
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != 0 -> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username android type: $inputType")
}
inputType and InputType.TYPE_TEXT_VARIATION_NORMAL != 0 ||
inputType and InputType.TYPE_NUMBER_VARIATION_NORMAL != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_PERSON_NAME != 0 -> {
usernameCandidate = autofillId
Log.d(TAG, "Autofill username candidate android type: $inputType")
}
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != 0 ||
inputType and InputType.TYPE_NUMBER_VARIATION_PASSWORD != 0 -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password android type: $inputType")
return true
}
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_FILTER != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_PHONETIC != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_URI != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != 0 -> {
// Type not used
}
else -> {
Log.d(TAG, "Autofill unknown android type: $inputType")
usernameCandidate = autofillId
}
}
}
return false
}
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
internal class Result { internal class Result {
var applicationId: String? = null
var domain: String? = null
set(value) {
if (field == null)
field = value
}
var usernameId: AutofillId? = null var usernameId: AutofillId? = null
set(value) { set(value) {
if (field == null) if (field == null)

View File

@@ -19,15 +19,11 @@
*/ */
package com.kunzisoft.keepass.backup package com.kunzisoft.keepass.backup
import android.annotation.SuppressLint
import android.app.backup.BackupAgentHelper import android.app.backup.BackupAgentHelper
import android.app.backup.SharedPreferencesBackupHelper import android.app.backup.SharedPreferencesBackupHelper
@SuppressLint("NewApi")
class SettingsBackupAgent : BackupAgentHelper() { class SettingsBackupAgent : BackupAgentHelper() {
//TODO Backup
override fun onCreate() { override fun onCreate() {
val defaultPrefs = this.packageName + "_preferences" val defaultPrefs = this.packageName + "_preferences"
val prefHelper = SharedPreferencesBackupHelper(this, defaultPrefs) val prefHelper = SharedPreferencesBackupHelper(this, defaultPrefs)

View File

@@ -300,7 +300,6 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) { override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec) loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec)
// TODO setAdvancedUnlockedMessageView(R.string.encrypted_value_stored)
} }
override fun handleDecryptedResult(decryptedValue: String) { override fun handleDecryptedResult(decryptedValue: String) {

View File

@@ -60,7 +60,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
setTitle(context.getString(R.string.biometric_prompt_store_credential_title)) setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
setDescription(context.getString(R.string.biometric_prompt_store_credential_message)) setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
setConfirmationRequired(true) setConfirmationRequired(true)
// TODO device credential // TODO device credential #102 #152
/* /*
if (keyguardManager?.isDeviceSecure == true) if (keyguardManager?.isDeviceSecure == true)
setDeviceCredentialAllowed(true) setDeviceCredentialAllowed(true)
@@ -73,7 +73,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title)) setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message)) //setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
setConfirmationRequired(false) setConfirmationRequired(false)
// TODO device credential // TODO device credential #102 #152
/* /*
if (keyguardManager?.isDeviceSecure == true) if (keyguardManager?.isDeviceSecure == true)
setDeviceCredentialAllowed(true) setDeviceCredentialAllowed(true)

View File

@@ -24,7 +24,7 @@ import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.engine.TwofishEngine import com.kunzisoft.keepass.crypto.engine.TwofishEngine
import org.spongycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.Security import java.security.Security
import java.util.* import java.util.*
@@ -37,6 +37,7 @@ object CipherFactory {
private var blacklisted: Boolean = false private var blacklisted: Boolean = false
init { init {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider()) Security.addProvider(BouncyCastleProvider())
} }

View File

@@ -19,16 +19,18 @@
*/ */
package com.kunzisoft.keepass.crypto package com.kunzisoft.keepass.crypto
enum class CrsAlgorithm constructor(val id: Int) { import com.kunzisoft.keepass.utils.UnsignedInt
Null(0), enum class CrsAlgorithm constructor(val id: UnsignedInt) {
ArcFourVariant(1),
Salsa20(2), Null(UnsignedInt(0)),
ChaCha20(3); ArcFourVariant(UnsignedInt(1)),
Salsa20(UnsignedInt(2)),
ChaCha20(UnsignedInt(3));
companion object { companion object {
fun fromId(num: Int): CrsAlgorithm? { fun fromId(num: UnsignedInt): CrsAlgorithm? {
for (e in values()) { for (e in values()) {
if (e.id == num) { if (e.id == num) {
return e return e

View File

@@ -19,11 +19,11 @@
*/ */
package com.kunzisoft.keepass.crypto package com.kunzisoft.keepass.crypto
import org.spongycastle.crypto.StreamCipher import org.bouncycastle.crypto.StreamCipher
import org.spongycastle.crypto.engines.ChaCha7539Engine import org.bouncycastle.crypto.engines.ChaCha7539Engine
import org.spongycastle.crypto.engines.Salsa20Engine import org.bouncycastle.crypto.engines.Salsa20Engine
import org.spongycastle.crypto.params.KeyParameter import org.bouncycastle.crypto.params.KeyParameter
import org.spongycastle.crypto.params.ParametersWithIV import org.bouncycastle.crypto.params.ParametersWithIV
object StreamCipherFactory { object StreamCipherFactory {

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.spongycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException

View File

@@ -17,18 +17,20 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.finalkey; package com.kunzisoft.keepass.crypto.finalkey
import com.kunzisoft.keepass.crypto.CipherFactory; import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
public class FinalKeyFactory { object AESKeyTransformerFactory : KeyTransformer() {
public static FinalKey createFinalKey() { override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
// Prefer the native final key implementation // Prefer the native final key implementation
if ( !CipherFactory.INSTANCE.deviceBlacklisted() && NativeFinalKey.available() ) { val keyTransformer = if (!deviceBlacklisted()
return new NativeFinalKey(); && NativeAESKeyTransformer.available()) {
NativeAESKeyTransformer()
} else { } else {
// Fall back on the android crypto implementation // Fall back on the android crypto implementation
return new AndroidFinalKey(); AndroidAESKeyTransformer()
} }
return keyTransformer.transformMasterKey(seed, key, rounds)
} }
} }

View File

@@ -0,0 +1,75 @@
/*
* 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.crypto.finalkey
import java.io.IOException
import java.lang.Exception
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.ShortBufferException
import javax.crypto.spec.SecretKeySpec
class AndroidAESKeyTransformer : KeyTransformer() {
@Throws(IOException::class)
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
val cipher: Cipher = try {
Cipher.getInstance("AES/ECB/NoPadding")
} catch (e: Exception) {
throw IOException("Unable to get the cipher", e)
}
try {
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(seed, "AES"))
} catch (e: InvalidKeyException) {
throw IOException("Unable to init the cipher", e)
}
if (key == null) {
throw IOException("Invalid key")
}
if (rounds == null) {
throw IOException("Invalid rounds")
}
// Encrypt key rounds times
val keyLength = key.size
val newKey = ByteArray(keyLength)
System.arraycopy(key, 0, newKey, 0, keyLength)
val destKey = ByteArray(keyLength)
for (i in 0 until rounds) {
try {
cipher.update(newKey, 0, newKey.size, destKey, 0)
System.arraycopy(destKey, 0, newKey, 0, newKey.size)
} catch (e: ShortBufferException) {
throw IOException("Short buffer", e)
}
}
// Hash the key
val messageDigest: MessageDigest = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here: " + e.message)
}
messageDigest.update(newKey)
return messageDigest.digest()
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2017 Brian Pellin, 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.crypto.finalkey;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
public class AndroidFinalKey extends FinalKey {
@Override
public byte[] transformMasterKey(byte[] pKeySeed, byte[] pKey, long rounds) throws IOException {
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/ECB/NoPadding");
} catch (NoSuchAlgorithmException e) {
throw new IOException("NoSuchAlgorithm: " + e.getMessage());
} catch (NoSuchPaddingException e) {
throw new IOException("NoSuchPadding: " + e.getMessage());
}
try {
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pKeySeed, "AES"));
} catch (InvalidKeyException e) {
throw new IOException("InvalidPasswordException: " + e.getMessage());
}
// Encrypt key rounds times
byte[] newKey = new byte[pKey.length];
System.arraycopy(pKey, 0, newKey, 0, pKey.length);
byte[] destKey = new byte[pKey.length];
for (int i = 0; i < rounds; i++) {
try {
cipher.update(newKey, 0, newKey.length, destKey, 0);
System.arraycopy(destKey, 0, newKey, 0, newKey.length);
} catch (ShortBufferException e) {
throw new IOException("Short buffer: " + e.getMessage());
}
}
// Hash the key
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
assert true;
throw new IOException("SHA-256 not implemented here: " + e.getMessage());
}
md.update(newKey);
return md.digest();
}
}

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.
* *
@@ -17,10 +17,11 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.finalkey; package com.kunzisoft.keepass.crypto.finalkey
import java.io.IOException; import java.io.IOException
public abstract class FinalKey { abstract class KeyTransformer {
public abstract byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException; @Throws(IOException::class)
} abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray?
}

View File

@@ -21,17 +21,20 @@ package com.kunzisoft.keepass.crypto.finalkey;
import com.kunzisoft.keepass.crypto.NativeLib; import com.kunzisoft.keepass.crypto.NativeLib;
import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
public class NativeFinalKey extends FinalKey { public class NativeAESKeyTransformer extends KeyTransformer {
public static boolean available() { public static boolean available() {
return NativeLib.INSTANCE.init(); return NativeLib.INSTANCE.init();
} }
@Nullable
@Override @Override
public byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException { public byte[] transformMasterKey(@Nullable byte[] seed, @Nullable byte[] key, @Nullable Long rounds) throws IOException {
NativeLib.INSTANCE.init(); NativeLib.INSTANCE.init();
return nTransformMasterKey(seed, key, rounds); return nTransformMasterKey(seed, key, rounds);

View File

@@ -22,72 +22,69 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources import android.content.res.Resources
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
import com.kunzisoft.keepass.stream.bytes16ToUuid import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
class AesKdf internal constructor() : KdfEngine() { class AesKdf : KdfEngine() {
init {
uuid = CIPHER_UUID
}
override val defaultParameters: KdfParameters override val defaultParameters: KdfParameters
get() { get() {
return KdfParameters(uuid!!).apply { return KdfParameters(uuid!!).apply {
setParamUUID() setParamUUID()
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong()) setUInt64(PARAM_ROUNDS, defaultKeyRounds)
} }
} }
override val defaultKeyRounds: Long override val defaultKeyRounds: Long = 6000L
get() = DEFAULT_ROUNDS.toLong()
init {
uuid = CIPHER_UUID
}
override fun getName(resources: Resources): String { override fun getName(resources: Resources): String {
return resources.getString(R.string.kdf_AES) return resources.getString(R.string.kdf_AES)
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray { override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
var currentMasterKey = masterKey
val rounds = p.getUInt64(PARAM_ROUNDS)
var seed = p.getByteArray(PARAM_SEED)
var seed = kdfParameters.getByteArray(PARAM_SEED)
if (seed != null && seed.size != 32) {
seed = CryptoUtil.hashSha256(seed)
}
var currentMasterKey = masterKey
if (currentMasterKey.size != 32) { if (currentMasterKey.size != 32) {
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey) currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
} }
if (seed.size != 32) { val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)
seed = CryptoUtil.hashSha256(seed)
}
val key = FinalKeyFactory.createFinalKey() return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
return key.transformMasterKey(seed, currentMasterKey, rounds)
} }
override fun randomize(p: KdfParameters) { override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom() val random = SecureRandom()
val seed = ByteArray(32) val seed = ByteArray(32)
random.nextBytes(seed) random.nextBytes(seed)
p.setByteArray(PARAM_SEED, seed) kdfParameters.setByteArray(PARAM_SEED, seed)
} }
override fun getKeyRounds(p: KdfParameters): Long { override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return p.getUInt64(PARAM_ROUNDS) return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds
} }
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) { override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
p.setUInt64(PARAM_ROUNDS, keyRounds) kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds)
} }
companion object { companion object {
private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = bytes16ToUuid( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xC9.toByte(), byteArrayOf(0xC9.toByte(),
0xD9.toByte(), 0xD9.toByte(),
@@ -106,7 +103,7 @@ class AesKdf internal constructor() : KdfEngine() {
0x4F.toByte(), 0x4F.toByte(),
0xEA.toByte())) 0xEA.toByte()))
const val PARAM_ROUNDS = "R" const val PARAM_ROUNDS = "R" // UInt64
const val PARAM_SEED = "S" const val PARAM_SEED = "S" // Byte array
} }
} }

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.
* *
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources import android.content.res.Resources
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 java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
@@ -53,35 +54,49 @@ class Argon2Kdf internal constructor() : KdfEngine() {
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray { override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
val salt = p.getByteArray(PARAM_SALT) val salt = kdfParameters.getByteArray(PARAM_SALT)
val parallelism = p.getUInt32(PARAM_PARALLELISM).toInt() val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
val memory = p.getUInt64(PARAM_MEMORY) UnsignedInt(it)
val iterations = p.getUInt64(PARAM_ITERATIONS) }
val version = p.getUInt32(PARAM_VERSION) val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let {
val secretKey = p.getByteArray(PARAM_SECRET_KEY) UnsignedInt.fromLong(it)
val assocData = p.getByteArray(PARAM_ASSOC_DATA) }
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.let {
UnsignedInt.fromLong(it)
}
val version = kdfParameters.getUInt32(PARAM_VERSION)?.let {
UnsignedInt(it)
}
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
return Argon2Native.transformKey(masterKey, salt, parallelism, memory, iterations, return Argon2Native.transformKey(masterKey,
secretKey, assocData, version) salt,
parallelism,
memory,
iterations,
secretKey,
assocData,
version)
} }
override fun randomize(p: KdfParameters) { override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom() val random = SecureRandom()
val salt = ByteArray(32) val salt = ByteArray(32)
random.nextBytes(salt) random.nextBytes(salt)
p.setByteArray(PARAM_SALT, salt) kdfParameters.setByteArray(PARAM_SALT, salt)
} }
override fun getKeyRounds(p: KdfParameters): Long { override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return p.getUInt64(PARAM_ITERATIONS) return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds
} }
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) { override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
p.setUInt64(PARAM_ITERATIONS, keyRounds) kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds)
} }
override val minKeyRounds: Long override val minKeyRounds: Long
@@ -90,12 +105,12 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxKeyRounds: Long override val maxKeyRounds: Long
get() = MAX_ITERATIONS get() = MAX_ITERATIONS
override fun getMemoryUsage(p: KdfParameters): Long { override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return p.getUInt64(PARAM_MEMORY) return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage
} }
override fun setMemoryUsage(p: KdfParameters, memory: Long) { override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
p.setUInt64(PARAM_MEMORY, memory) kdfParameters.setUInt64(PARAM_MEMORY, memory)
} }
override val defaultMemoryUsage: Long override val defaultMemoryUsage: Long
@@ -107,21 +122,23 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxMemoryUsage: Long override val maxMemoryUsage: Long
get() = MAX_MEMORY get() = MAX_MEMORY
override fun getParallelism(p: KdfParameters): Int { override fun getParallelism(kdfParameters: KdfParameters): Long {
return p.getUInt32(PARAM_PARALLELISM).toInt() // TODO Verify return kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
UnsignedInt(it).toLong()
} ?: defaultParallelism
} }
override fun setParallelism(p: KdfParameters, parallelism: Int) { override fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
p.setUInt32(PARAM_PARALLELISM, parallelism.toLong()) kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
} }
override val defaultParallelism: Int override val defaultParallelism: Long
get() = DEFAULT_PARALLELISM.toInt() get() = DEFAULT_PARALLELISM.toLong()
override val minParallelism: Int override val minParallelism: Long
get() = MIN_PARALLELISM get() = MIN_PARALLELISM
override val maxParallelism: Int override val maxParallelism: Long
get() = MAX_PARALLELISM get() = MAX_PARALLELISM
companion object { companion object {
@@ -152,23 +169,24 @@ class Argon2Kdf internal constructor() : KdfEngine() {
private const val PARAM_SECRET_KEY = "K" // byte[] private const val PARAM_SECRET_KEY = "K" // byte[]
private const val PARAM_ASSOC_DATA = "A" // byte[] private const val PARAM_ASSOC_DATA = "A" // byte[]
private const val MIN_VERSION: Long = 0x10 private val MIN_VERSION = UnsignedInt(0x10)
private const val MAX_VERSION: Long = 0x13 private val MAX_VERSION = UnsignedInt(0x13)
private const val MIN_SALT = 8 private const val MIN_SALT = 8
private const val MAX_SALT = Integer.MAX_VALUE private val MAX_SALT = UnsignedInt.MAX_VALUE.toLong()
private const val MIN_ITERATIONS: Long = 1 private const val MIN_ITERATIONS: Long = 1L
private const val MAX_ITERATIONS = 4294967295L private const val MAX_ITERATIONS = 4294967295L
private const val MIN_MEMORY = (1024 * 8).toLong() private const val MIN_MEMORY = (1024 * 8).toLong()
private const val MAX_MEMORY = Integer.MAX_VALUE.toLong() private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toLong()
private const val MEMORY_BLOCK_SIZE: Long = 1024L
private const val MIN_PARALLELISM = 1 private const val MIN_PARALLELISM: Long = 1L
private const val MAX_PARALLELISM = (1 shl 24) - 1 private const val MAX_PARALLELISM: Long = ((1 shl 24) - 1).toLong()
private const val DEFAULT_ITERATIONS: Long = 2 private const val DEFAULT_ITERATIONS: Long = 2L
private const val DEFAULT_MEMORY = (1024 * 1024).toLong() private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
private const val DEFAULT_PARALLELISM: Long = 2 private val DEFAULT_PARALLELISM = UnsignedInt(2)
} }
} }

View File

@@ -20,20 +20,29 @@
package com.kunzisoft.keepass.crypto.keyDerivation; package com.kunzisoft.keepass.crypto.keyDerivation;
import com.kunzisoft.keepass.crypto.NativeLib; import com.kunzisoft.keepass.crypto.NativeLib;
import com.kunzisoft.keepass.utils.UnsignedInt;
import java.io.IOException; import java.io.IOException;
public class Argon2Native { public class Argon2Native {
public static byte[] transformKey(byte[] password, byte[] salt, int parallelism, public static byte[] transformKey(byte[] password, byte[] salt, UnsignedInt parallelism,
long memory, long iterations, byte[] secretKey, UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
byte[] associatedData, long version) throws IOException { byte[] associatedData, UnsignedInt version) throws IOException {
NativeLib.INSTANCE.init(); NativeLib.INSTANCE.init();
return nTransformMasterKey(password, salt, parallelism, memory, iterations, secretKey, associatedData, version); return nTransformMasterKey(
password,
salt,
parallelism.toInt(),
memory.toInt(),
iterations.toInt(),
secretKey,
associatedData,
version.toInt());
} }
private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism, private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism,
long memory, long iterations, byte[] secretKey, int memory, int iterations, byte[] secretKey,
byte[] associatedData, long version) throws IOException; byte[] associatedData, int version) throws IOException;
} }

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.utils.ObjectNameResource import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable
@@ -33,17 +34,17 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
abstract val defaultParameters: KdfParameters abstract val defaultParameters: KdfParameters
@Throws(IOException::class) @Throws(IOException::class)
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray abstract fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray
abstract fun randomize(p: KdfParameters) abstract fun randomize(kdfParameters: KdfParameters)
/* /*
* ITERATIONS * ITERATIONS
*/ */
abstract fun getKeyRounds(p: KdfParameters): Long abstract fun getKeyRounds(kdfParameters: KdfParameters): Long
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long) abstract fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long)
abstract val defaultKeyRounds: Long abstract val defaultKeyRounds: Long
@@ -51,51 +52,51 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
get() = 1 get() = 1
open val maxKeyRounds: Long open val maxKeyRounds: Long
get() = Int.MAX_VALUE.toLong() get() = UnsignedInt.MAX_VALUE.toLong()
/* /*
* MEMORY * MEMORY
*/ */
open fun getMemoryUsage(p: KdfParameters): Long { open fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE.toLong() return UNKNOWN_VALUE
} }
open fun setMemoryUsage(p: KdfParameters, memory: Long) { open fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
// Do nothing by default // Do nothing by default
} }
open val defaultMemoryUsage: Long open val defaultMemoryUsage: Long
get() = UNKNOWN_VALUE.toLong() get() = UNKNOWN_VALUE
open val minMemoryUsage: Long open val minMemoryUsage: Long
get() = 1 get() = 1
open val maxMemoryUsage: Long open val maxMemoryUsage: Long
get() = Int.MAX_VALUE.toLong() get() = UnsignedInt.MAX_VALUE.toLong()
/* /*
* PARALLELISM * PARALLELISM
*/ */
open fun getParallelism(p: KdfParameters): Int { open fun getParallelism(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE return UNKNOWN_VALUE
} }
open fun setParallelism(p: KdfParameters, parallelism: Int) { open fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
// Do nothing by default // Do nothing by default
} }
open val defaultParallelism: Int open val defaultParallelism: Long
get() = UNKNOWN_VALUE get() = UNKNOWN_VALUE
open val minParallelism: Int open val minParallelism: Long
get() = 1 get() = 1L
open val maxParallelism: Int open val maxParallelism: Long
get() = Int.MAX_VALUE get() = UnsignedInt.MAX_VALUE.toLong()
companion object { companion object {
const val UNKNOWN_VALUE = -1 const val UNKNOWN_VALUE: Long = -1L
} }
} }

View File

@@ -29,7 +29,7 @@ import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() { class KdfParameters(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() { fun setParamUUID() {
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid)) setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
@@ -41,26 +41,25 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
@Throws(IOException::class) @Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? { fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data) val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data))
val lis = LittleEndianDataInputStream(bis) val dictionary = deserialize(inputStream)
val d = deserialize(lis) ?: return null val uuidBytes = dictionary.getByteArray(PARAM_UUID) ?: return null
val uuid = bytes16ToUuid(uuidBytes)
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID)) val kdfParameters = KdfParameters(uuid)
kdfParameters.copyTo(dictionary)
val kdfP = KdfParameters(uuid) return kdfParameters
kdfP.copyTo(d)
return kdfP
} }
@Throws(IOException::class) @Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray { fun serialize(kdfParameters: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
val los = LittleEndianDataOutputStream(bos) val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream)
serialize(kdf, los) serialize(kdfParameters, outputStream)
return bos.toByteArray() return byteArrayOutputStream.toByteArray()
} }
} }

View File

@@ -30,7 +30,8 @@ import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
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.* import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm 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
@@ -54,6 +55,8 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_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_ITERATIONS_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
@@ -61,12 +64,10 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_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.getBundleFromListNodes import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.retrieveProgressDialog import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION 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
@@ -85,26 +86,25 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
private var serviceConnection: ServiceConnection? = null private var serviceConnection: ServiceConnection? = null
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = 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?) {
TimeoutHelper.temporarilyDisableTimeout() TimeoutHelper.temporarilyDisableTimeout()
// Stop the opening notification // Stop the opening notification
DatabaseOpenNotificationService.stop(activity) DatabaseOpenNotificationService.stop(activity)
startOrUpdateDialog(titleId, messageId, warningId) startOrUpdateDialog(titleId, messageId, warningId)
} }
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) { override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
TimeoutHelper.temporarilyDisableTimeout()
// Stop the opening notification
DatabaseOpenNotificationService.stop(activity)
startOrUpdateDialog(titleId, messageId, warningId) startOrUpdateDialog(titleId, messageId, warningId)
} }
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) { override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(actionTask, result) onActionFinish?.invoke(actionTask, result)
// Remove the progress task // Remove the progress task
ProgressTaskDialogFragment.stop(activity) stopDialog()
TimeoutHelper.releaseTemporarilyDisableTimeout() TimeoutHelper.releaseTemporarilyDisableTimeout()
val inTime = if (activity is LockingActivity) { val inTime = if (activity is LockingActivity) {
@@ -121,12 +121,15 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
} }
private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?) { private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
var progressTaskDialogFragment = retrieveProgressDialog(activity)
if (progressTaskDialogFragment == null) { if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = ProgressTaskDialogFragment.build() progressTaskDialogFragment = activity.supportFragmentManager
ProgressTaskDialogFragment.start(activity, progressTaskDialogFragment) .findFragmentByTag(PROGRESS_TASK_DIALOG_TAG) as ProgressTaskDialogFragment?
} }
progressTaskDialogFragment.apply { if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = ProgressTaskDialogFragment()
progressTaskDialogFragment?.show(activity.supportFragmentManager, PROGRESS_TASK_DIALOG_TAG)
}
progressTaskDialogFragment?.apply {
titleId?.let { titleId?.let {
updateTitle(it) updateTitle(it)
} }
@@ -139,7 +142,11 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
} }
} }
@Synchronized private fun stopDialog() {
progressTaskDialogFragment?.dismissAllowingStateLoss()
progressTaskDialogFragment = null
}
private fun initServiceConnection() { private fun initServiceConnection() {
if (serviceConnection == null) { if (serviceConnection == null) {
serviceConnection = object : ServiceConnection { serviceConnection = object : ServiceConnection {
@@ -158,7 +165,6 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
} }
} }
@Synchronized
private fun bindService() { private fun bindService() {
initServiceConnection() initServiceConnection()
serviceConnection?.let { serviceConnection?.let {
@@ -169,7 +175,6 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
/** /**
* Unbind the service and assign null to the service connection to check if already unbind or not * Unbind the service and assign null to the service connection to check if already unbind or not
*/ */
@Synchronized
private fun unBindService() { private fun unBindService() {
serviceConnection?.let { serviceConnection?.let {
activity.unbindService(it) activity.unbindService(it)
@@ -177,22 +182,19 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
serviceConnection = null serviceConnection = null
} }
@Synchronized
fun registerProgressTask() { fun registerProgressTask() {
ProgressTaskDialogFragment.stop(activity) stopDialog()
// Register a database task receiver to stop loading dialog when service finish the task // Register a database task receiver to stop loading dialog when service finish the task
databaseTaskBroadcastReceiver = object : BroadcastReceiver() { databaseTaskBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
activity.runOnUiThread { when (intent?.action) {
when (intent?.action) { DATABASE_START_TASK_ACTION -> {
DATABASE_START_TASK_ACTION -> { // Bind to the service when is starting
// Bind to the service when is starting bindService()
bindService() }
} DATABASE_STOP_TASK_ACTION -> {
DATABASE_STOP_TASK_ACTION -> { unBindService()
unBindService()
}
} }
} }
} }
@@ -208,9 +210,8 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
bindService() bindService()
} }
@Synchronized
fun unregisterProgressTask() { fun unregisterProgressTask() {
ProgressTaskDialogFragment.stop(activity) stopDialog()
mBinder?.removeActionTaskListener(actionTaskListener) mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null mBinder = null
@@ -224,18 +225,15 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
} }
} }
@Synchronized
private fun start(bundle: Bundle? = null, actionTask: String) { private fun start(bundle: Bundle? = null, actionTask: String) {
activity.stopService(intentDatabaseTask) activity.stopService(intentDatabaseTask)
if (bundle != null) if (bundle != null)
intentDatabaseTask.putExtras(bundle) intentDatabaseTask.putExtras(bundle)
activity.runOnUiThread {
intentDatabaseTask.action = actionTask intentDatabaseTask.action = actionTask
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(intentDatabaseTask) activity.startForegroundService(intentDatabaseTask)
} else { } else {
activity.startService(intentDatabaseTask) activity.startService(intentDatabaseTask)
}
} }
} }
@@ -552,12 +550,12 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK) , ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
} }
fun startDatabaseSaveParallelism(oldParallelism: Int, fun startDatabaseSaveParallelism(oldParallelism: Long,
newParallelism: Int, newParallelism: Long,
save: Boolean) { save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism) putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK) , ACTION_DATABASE_UPDATE_PARALLELISM_TASK)

View File

@@ -20,15 +20,11 @@
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.cursor.EntryCursorKDB
import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
@@ -48,8 +44,10 @@ import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.stream.readBytes4ToInt import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
@@ -137,6 +135,9 @@ class Database {
val version: String val version: String
get() = mDatabaseKDB?.version ?: mDatabaseKDBX?.version ?: "-" get() = mDatabaseKDB?.version ?: mDatabaseKDBX?.version ?: "-"
val type: Class<*>?
get() = mDatabaseKDB?.javaClass ?: mDatabaseKDBX?.javaClass
val allowDataCompression: Boolean val allowDataCompression: Boolean
get() = mDatabaseKDBX != null get() = mDatabaseKDBX != null
@@ -205,7 +206,6 @@ class Database {
var numberKeyEncryptionRounds: Long var numberKeyEncryptionRounds: Long
get() = mDatabaseKDB?.numberKeyEncryptionRounds ?: mDatabaseKDBX?.numberKeyEncryptionRounds ?: 0 get() = mDatabaseKDB?.numberKeyEncryptionRounds ?: mDatabaseKDBX?.numberKeyEncryptionRounds ?: 0
@Throws(NumberFormatException::class)
set(numberRounds) { set(numberRounds) {
mDatabaseKDB?.numberKeyEncryptionRounds = numberRounds mDatabaseKDB?.numberKeyEncryptionRounds = numberRounds
mDatabaseKDBX?.numberKeyEncryptionRounds = numberRounds mDatabaseKDBX?.numberKeyEncryptionRounds = numberRounds
@@ -213,13 +213,13 @@ class Database {
var memoryUsage: Long var memoryUsage: Long
get() { get() {
return mDatabaseKDBX?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE.toLong() return mDatabaseKDBX?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE
} }
set(memory) { set(memory) {
mDatabaseKDBX?.memoryUsage = memory mDatabaseKDBX?.memoryUsage = memory
} }
var parallelism: Int var parallelism: Long
get() = mDatabaseKDBX?.parallelism ?: KdfEngine.UNKNOWN_VALUE get() = mDatabaseKDBX?.parallelism ?: KdfEngine.UNKNOWN_VALUE
set(parallelism) { set(parallelism) {
mDatabaseKDBX?.parallelism = parallelism mDatabaseKDBX?.parallelism = parallelism
@@ -338,7 +338,10 @@ class Database {
} }
// Load Data, pass Uris as InputStreams // Load Data, pass Uris as InputStreams
databaseInputStream = BufferedInputStream(UriUtil.getUriInputStream(contentResolver, uri)) val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
?: throw IOException("Database input stream cannot be retrieve")
databaseInputStream = BufferedInputStream(databaseStream)
if (!databaseInputStream.markSupported()) { if (!databaseInputStream.markSupported()) {
throw IOException("Input stream does not support mark.") throw IOException("Input stream does not support mark.")
} }
@@ -347,8 +350,8 @@ class Database {
databaseInputStream.mark(10) databaseInputStream.mark(10)
// Get the file directory to save the attachments // Get the file directory to save the attachments
val sig1 = databaseInputStream.readBytes4ToInt() val sig1 = databaseInputStream.readBytes4ToUInt()
val sig2 = databaseInputStream.readBytes4ToInt() val sig2 = databaseInputStream.readBytes4ToUInt()
// Return to the start // Return to the start
databaseInputStream.reset() databaseInputStream.reset()
@@ -397,54 +400,29 @@ class Database {
false false
} }
@JvmOverloads fun createVirtualGroupFromSearch(searchQuery: String,
fun search(str: String, max: Int = Integer.MAX_VALUE): Group? { max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.search(this, str, max) return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchQuery, SearchParameters(), max)
} }
fun searchEntries(context: Context, query: String): Cursor? { fun createVirtualGroupFromSearch(searchInfo: SearchInfo,
max: Int = Integer.MAX_VALUE): Group? {
var cursorKDB: EntryCursorKDB? = null val query = (if (searchInfo.webDomain != null)
var cursorKDBX: EntryCursorKDBX? = null searchInfo.webDomain
else
if (mDatabaseKDB != null) searchInfo.applicationId)
cursorKDB = EntryCursorKDB() ?: return null
if (mDatabaseKDBX != null) return mSearchHelper?.createVirtualGroupWithSearchResult(this, query, SearchParameters().apply {
cursorKDBX = EntryCursorKDBX() searchInTitles = false
searchInUserNames = false
val searchResult = search(query, SearchHelper.MAX_SEARCH_ENTRY) searchInPasswords = false
if (searchResult != null) { searchInUrls = true
// Search in hide entries but not meta-stream searchInNotes = true
for (entry in searchResult.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) { searchInOther = true
entry.entryKDB?.let { searchInUUIDs = false
cursorKDB?.addEntry(it) searchInTags = false
} ignoreCase = true
entry.entryKDBX?.let { }, max)
cursorKDBX?.addEntry(it)
}
}
}
return cursorKDB ?: cursorKDBX
}
fun getEntryFrom(cursor: Cursor): Entry? {
val iconFactory = mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory()
return createEntry()?.apply {
startManageEntry(this)
mDatabaseKDB?.let {
entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB, iconFactory)
}
}
mDatabaseKDBX?.let {
entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, iconFactory)
}
}
stopManageEntry(this)
}
} }
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)

View File

@@ -398,6 +398,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
database?.startManageEntry(this) database?.startManageEntry(this)
entryInfo.id = nodeId.toString() entryInfo.id = nodeId.toString()
entryInfo.title = title entryInfo.title = title
entryInfo.icon = icon
entryInfo.username = username entryInfo.username = username
entryInfo.password = password entryInfo.password = password
entryInfo.url = url entryInfo.url = url

View File

@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.* import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -251,6 +252,14 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
ArrayList() ArrayList()
} }
fun getChildEntriesInfo(database: Database): List<EntryInfo> {
val entriesInfo = ArrayList<EntryInfo>()
getChildEntries().forEach { entry ->
entriesInfo.add(entry.getEntryInfo(database))
}
return entriesInfo
}
fun getFilteredChildEntries(vararg filter: ChildFilter): List<Entry> { fun getFilteredChildEntries(vararg filter: ChildFilter): List<Entry> {
val withoutMetaStream = filter.contains(ChildFilter.META_STREAM) val withoutMetaStream = filter.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED) val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED)

View File

@@ -19,14 +19,13 @@
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
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.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.IOException import java.io.IOException
@@ -39,8 +38,6 @@ import kotlin.collections.ArrayList
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() { class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var numKeyEncRounds: Int = 0
var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
private var kdfListV3: MutableList<KdfEngine> = ArrayList() private var kdfListV3: MutableList<KdfEngine> = ArrayList()
@@ -88,19 +85,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
override val passwordEncoding: String override val passwordEncoding: String
get() = "ISO-8859-1" get() = "ISO-8859-1"
override var numberKeyEncryptionRounds: Long override var numberKeyEncryptionRounds = 300L
get() = numKeyEncRounds.toLong()
@Throws(NumberFormatException::class)
set(rounds) {
if (rounds > Integer.MAX_VALUE || rounds < Integer.MIN_VALUE) {
throw NumberFormatException()
}
numKeyEncRounds = rounds.toInt()
}
init { init {
algorithm = EncryptionAlgorithm.AESRijndael algorithm = EncryptionAlgorithm.AESRijndael
numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS
} }
/** /**
@@ -159,9 +147,9 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
val nos = NullOutputStream() val nos = NullOutputStream()
val dos = DigestOutputStream(nos, messageDigest) val dos = DigestOutputStream(nos, messageDigest)
val transformedMasterKey = transformMasterKey(masterSeed2, masterKey, numRounds) // Encrypt the master key a few times to make brute-force key-search harder
dos.write(masterSeed) dos.write(masterSeed)
dos.write(transformedMasterKey) dos.write(AESKeyTransformerFactory.transformMasterKey(masterSeed2, masterKey, numRounds) ?: ByteArray(0))
finalKey = messageDigest.digest() finalKey = messageDigest.digest()
} }
@@ -262,23 +250,11 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
} }
companion object { companion object {
val TYPE = DatabaseKDB::class.java
const val BACKUP_FOLDER_TITLE = "Backup" const val BACKUP_FOLDER_TITLE = "Backup"
private const val BACKUP_FOLDER_UNDEFINED_ID = -1 private const val BACKUP_FOLDER_UNDEFINED_ID = -1
private const val DEFAULT_ENCRYPTION_ROUNDS = 300
const val BUFFER_SIZE_BYTES = 3 * 128 const val BUFFER_SIZE_BYTES = 3 * 128
/**
* Encrypt the master key a few times to make brute-force key-search harder
* @throws IOException
*/
@Throws(IOException::class)
private fun transformMasterKey(pKeySeed: ByteArray, pKey: ByteArray, rounds: Long): ByteArray {
val key = FinalKeyFactory.createFinalKey()
return key.transformMasterKey(pKeySeed, pKey, rounds)
}
} }
} }

View File

@@ -29,7 +29,8 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
@@ -41,6 +42,7 @@ 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.UnsignedInt
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.Text import org.w3c.dom.Text
@@ -66,7 +68,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private var numKeyEncRounds: Long = 0 private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary() var publicCustomData = VariantDictionary()
var kdbxVersion: Long = 0 var kdbxVersion = UnsignedInt(0)
var name = "" var name = ""
var nameChanged = DateInstant() var nameChanged = DateInstant()
// TODO change setting date // TODO change setting date
@@ -76,13 +78,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var defaultUserName = "" var defaultUserName = ""
var defaultUserNameChanged = DateInstant() var defaultUserNameChanged = DateInstant()
// TODO date // TODO last change date
var keyLastChanged = DateInstant() var keyLastChanged = DateInstant()
var keyChangeRecDays: Long = -1 var keyChangeRecDays: Long = -1
var keyChangeForceDays: Long = 1 var keyChangeForceDays: Long = 1
var isKeyChangeForceOnce = false var isKeyChangeForceOnce = false
var maintenanceHistoryDays: Long = 365 var maintenanceHistoryDays = UnsignedInt(365)
var color = "" var color = ""
/** /**
* Determine if RecycleBin is enable or not * Determine if RecycleBin is enable or not
@@ -218,7 +220,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!) numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!)
return numKeyEncRounds return numKeyEncRounds
} }
@Throws(NumberFormatException::class)
set(rounds) { set(rounds) {
val kdfEngine = kdfEngine val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null) if (kdfEngine != null && kdfParameters != null)
@@ -231,7 +232,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val kdfEngine = kdfEngine val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) { return if (kdfEngine != null && kdfParameters != null) {
kdfEngine.getMemoryUsage(kdfParameters!!) kdfEngine.getMemoryUsage(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE.toLong() } else KdfEngine.UNKNOWN_VALUE
} }
set(memory) { set(memory) {
val kdfEngine = kdfEngine val kdfEngine = kdfEngine
@@ -239,7 +240,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
kdfEngine.setMemoryUsage(kdfParameters!!, memory) kdfEngine.setMemoryUsage(kdfParameters!!, memory)
} }
var parallelism: Int var parallelism: Long
get() { get() {
val kdfEngine = kdfEngine val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) { return if (kdfEngine != null && kdfParameters != null) {
@@ -551,6 +552,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
companion object { companion object {
val TYPE = DatabaseKDBX::class.java
private val TAG = DatabaseKDBX::class.java.name private val TAG = DatabaseKDBX::class.java.name
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited

View File

@@ -104,10 +104,7 @@ abstract class DatabaseVersioned<
} }
@Throws(IOException::class) @Throws(IOException::class)
protected fun getPasswordKey(key: String?): ByteArray { protected fun getPasswordKey(key: String): ByteArray {
if (key == null)
throw IllegalArgumentException("Key cannot be empty.") // TODO
val messageDigest: MessageDigest val messageDigest: MessageDigest
try { try {
messageDigest = MessageDigest.getInstance("SHA-256") messageDigest = MessageDigest.getInstance("SHA-256")

View File

@@ -23,6 +23,7 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.utils.ParcelableUtil import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedInt
import java.util.HashMap import java.util.HashMap
@@ -46,7 +47,7 @@ class AutoType : Parcelable {
constructor(parcel: Parcel) { constructor(parcel: Parcel) {
this.enabled = parcel.readByte().toInt() != 0 this.enabled = parcel.readByte().toInt() != 0
this.obfuscationOptions = parcel.readLong() this.obfuscationOptions = UnsignedInt(parcel.readInt())
this.defaultSequence = parcel.readString() ?: defaultSequence this.defaultSequence = parcel.readString() ?: defaultSequence
this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel) this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel)
} }
@@ -57,7 +58,7 @@ class AutoType : Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (enabled) 1 else 0).toByte()) dest.writeByte((if (enabled) 1 else 0).toByte())
dest.writeLong(obfuscationOptions) dest.writeInt(obfuscationOptions.toInt())
dest.writeString(defaultSequence) dest.writeString(defaultSequence)
ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs) ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs)
} }
@@ -71,7 +72,7 @@ class AutoType : Parcelable {
} }
companion object { companion object {
private const val OBF_OPT_NONE: Long = 0 private val OBF_OPT_NONE = UnsignedInt(0)
@JvmField @JvmField
val CREATOR: Parcelable.Creator<AutoType> = object : Parcelable.Creator<AutoType> { val CREATOR: Parcelable.Creator<AutoType> = object : Parcelable.Creator<AutoType> {

View File

@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.* import java.util.*
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface { class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
@@ -104,7 +105,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
usageCount = parcel.readLong() usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel) customData = ParcelableUtil.readStringParcelableMap(parcel)
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java) fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
@@ -122,7 +123,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeParcelable(iconCustom, flags) dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount) dest.writeLong(usageCount.toLong())
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData) ParcelableUtil.writeStringParcelableMap(dest, customData)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields) ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
@@ -243,7 +244,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
fields[STR_NOTES] = ProtectedString(protect, value) fields[STR_NOTES] = ProtectedString(protect, value)
} }
override var usageCount: Long = 0 override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant() override var locationChanged = DateInstant()
@@ -332,7 +333,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun touch(modified: Boolean, touchParents: Boolean) { override fun touch(modified: Boolean, touchParents: Boolean) {
super.touch(modified, touchParents) super.touch(modified, touchParents)
++usageCount // TODO unsigned long
usageCount = UnsignedLong(usageCount.toLong() + 1)
} }
companion object { companion object {

View File

@@ -22,8 +22,7 @@ package com.kunzisoft.keepass.database.element.entry
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler
import com.kunzisoft.keepass.database.search.SearchParametersKDBX import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.utils.StringUtil
import java.util.* import java.util.*
class FieldReferencesEngine { class FieldReferencesEngine {
@@ -76,11 +75,11 @@ class FieldReferencesEngine {
for (i in 0..19) { for (i in 0..19) {
text = fillRefsUsingCache(text, contextV4) text = fillRefsUsingCache(text, contextV4)
val start = StringUtil.indexOfIgnoreCase(text, STR_REF_START, offset, Locale.ENGLISH) val start = text.indexOf(STR_REF_START, offset, true)
if (start < 0) { if (start < 0) {
break break
} }
val end = StringUtil.indexOfIgnoreCase(text, STR_REF_END, start + 1, Locale.ENGLISH) val end = text.indexOf(STR_REF_END, start + 1, true)
if (end <= start) { if (end <= start) {
break break
} }
@@ -142,24 +141,23 @@ class FieldReferencesEngine {
val scan = Character.toUpperCase(ref[2]) val scan = Character.toUpperCase(ref[2])
val wanted = Character.toUpperCase(ref[0]) val wanted = Character.toUpperCase(ref[0])
val searchParametersV4 = SearchParametersKDBX() val searchParameters = SearchParameters()
searchParametersV4.setupNone() searchParameters.setupNone()
searchParametersV4.searchString = ref.substring(4) searchParameters.searchString = ref.substring(4)
when (scan) { when (scan) {
'T' -> searchParametersV4.searchInTitles = true 'T' -> searchParameters.searchInTitles = true
'U' -> searchParametersV4.searchInUserNames = true 'U' -> searchParameters.searchInUserNames = true
'A' -> searchParametersV4.searchInUrls = true 'A' -> searchParameters.searchInUrls = true
'P' -> searchParametersV4.searchInPasswords = true 'P' -> searchParameters.searchInPasswords = true
'N' -> searchParametersV4.searchInNotes = true 'N' -> searchParameters.searchInNotes = true
'I' -> searchParametersV4.searchInUUIDs = true 'I' -> searchParameters.searchInUUIDs = true
'O' -> searchParametersV4.searchInOther = true 'O' -> searchParameters.searchInOther = true
else -> return null else -> return null
} }
val list = ArrayList<EntryKDBX>() val list = ArrayList<EntryKDBX>()
// TODO type parameter searchEntries(contextV4.databaseV4?.rootGroup, searchParameters, list)
searchEntries(contextV4.databaseV4!!.rootGroup, searchParametersV4, list)
return if (list.size > 0) { return if (list.size > 0) {
TargetResult(list[0], wanted) TargetResult(list[0], wanted)
@@ -186,22 +184,22 @@ class FieldReferencesEngine {
private fun fillRefsUsingCache(text: String, sprContextV4: SprContextV4): String { private fun fillRefsUsingCache(text: String, sprContextV4: SprContextV4): String {
var newText = text var newText = text
for ((key, value) in sprContextV4.refsCache) { for ((key, value) in sprContextV4.refsCache) {
newText = StringUtil.replaceAllIgnoresCase(text, key, value, Locale.ENGLISH) newText = text.replace(key, value, true)
} }
return newText return newText
} }
private fun searchEntries(root: GroupKDBX?, searchParametersV4: SearchParametersKDBX?, listStorage: MutableList<EntryKDBX>?) { private fun searchEntries(root: GroupKDBX?, searchParameters: SearchParameters?, listStorage: MutableList<EntryKDBX>?) {
if (searchParametersV4 == null) { if (searchParameters == null) {
return return
} }
if (listStorage == null) { if (listStorage == null) {
return return
} }
val terms = StringUtil.splitStringTerms(searchParametersV4.searchString) val terms = splitStringTerms(searchParameters.searchString)
if (terms.size <= 1 || searchParametersV4.regularExpression) { if (terms.size <= 1 || searchParameters.regularExpression) {
root!!.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, listStorage), null) root!!.doForEachChild(EntryKDBXSearchHandler(searchParameters, listStorage), null)
return return
} }
@@ -209,41 +207,76 @@ class FieldReferencesEngine {
val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length } val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length }
Collections.sort(terms, stringLengthComparator) Collections.sort(terms, stringLengthComparator)
val fullSearch = searchParametersV4.searchString val fullSearch = searchParameters.searchString
var childEntries: List<EntryKDBX>? = root!!.getChildEntries() var childEntries: List<EntryKDBX>? = root!!.getChildEntries()
for (i in terms.indices) { for (i in terms.indices) {
val pgNew = ArrayList<EntryKDBX>() val pgNew = ArrayList<EntryKDBX>()
searchParametersV4.searchString = terms[i] searchParameters.searchString = terms[i]
var negate = false var negate = false
if (searchParametersV4.searchString.startsWith("-")) { if (searchParameters.searchString.startsWith("-")) {
searchParametersV4.searchString = searchParametersV4.searchString.substring(1) searchParameters.searchString = searchParameters.searchString.substring(1)
negate = searchParametersV4.searchString.isNotEmpty() negate = searchParameters.searchString.isNotEmpty()
} }
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, pgNew), null)) { if (!root.doForEachChild(EntryKDBXSearchHandler(searchParameters, pgNew), null)) {
childEntries = null childEntries = null
break break
} }
val complement = ArrayList<EntryKDBX>() childEntries = if (negate) {
if (negate) { val complement = ArrayList<EntryKDBX>()
for (entry in childEntries!!) { for (entry in childEntries!!) {
if (!pgNew.contains(entry)) { if (!pgNew.contains(entry)) {
complement.add(entry) complement.add(entry)
} }
} }
childEntries = complement complement
} else { } else {
childEntries = pgNew pgNew
} }
} }
if (childEntries != null) { if (childEntries != null) {
listStorage.addAll(childEntries) listStorage.addAll(childEntries)
} }
searchParametersV4.searchString = fullSearch searchParameters.searchString = fullSearch
}
/**
* Create a list of String by split text when ' ', '\t', '\r' or '\n' is found
*/
private fun splitStringTerms(text: String?): List<String> {
val list = ArrayList<String>()
if (text == null) {
return list
}
val stringBuilder = StringBuilder()
var quoted = false
for (element in text) {
if ((element == ' ' || element == '\t' || element == '\r' || element == '\n') && !quoted) {
val len = stringBuilder.length
when {
len > 0 -> {
list.add(stringBuilder.toString())
stringBuilder.delete(0, len)
}
element == '\"' -> quoted = !quoted
else -> stringBuilder.append(element)
}
}
}
if (stringBuilder.isNotEmpty()) {
list.add(stringBuilder.toString())
}
return list
} }
companion object { companion object {

View File

@@ -32,14 +32,14 @@ import java.util.*
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface { class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
var level = 0 // short var level = 0 // short
/** Used by KeePass internally, don't use */ // Used by KeePass internally, don't use
var flags: Int = 0 var groupFlags = 0
constructor() : super() constructor() : super()
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
level = parcel.readInt() level = parcel.readInt()
flags = parcel.readInt() groupFlags = parcel.readInt()
} }
override fun readParentParcelable(parcel: Parcel): GroupKDB? { override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -53,13 +53,13 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeInt(level) dest.writeInt(level)
dest.writeInt(flags) dest.writeInt(groupFlags)
} }
fun updateWith(source: GroupKDB) { fun updateWith(source: GroupKDB) {
super.updateWith(source) super.updateWith(source)
level = source.level level = source.level
flags = source.flags groupFlags = source.groupFlags
} }
override val type: Type override val type: Type

View File

@@ -31,6 +31,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
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.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.HashMap import java.util.HashMap
import java.util.UUID import java.util.UUID
@@ -77,9 +78,9 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
usageCount = parcel.readLong() usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
// TODO customData = ParcelableUtil.readStringParcelableMap(in); // TODO customData = ParcelableUtil.readStringParcelableMap(parcel);
notes = parcel.readString() ?: notes notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0 isExpanded = parcel.readByte().toInt() != 0
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
@@ -101,7 +102,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeParcelable(iconCustom, flags) dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount) dest.writeLong(usageCount.toLong())
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData); // TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
dest.writeString(notes) dest.writeString(notes)
@@ -130,7 +131,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
lastTopVisibleEntry = source.lastTopVisibleEntry lastTopVisibleEntry = source.lastTopVisibleEntry
} }
override var usageCount: Long = 0 override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant() override var locationChanged = DateInstant()

View File

@@ -49,7 +49,7 @@ class IconImageCustom : IconImage {
constructor(parcel: Parcel) { constructor(parcel: Parcel) {
uuid = parcel.readSerializable() as UUID uuid = parcel.readSerializable() as UUID
// TODO Take too much memories // TODO Take too much memories
// in.readByteArray(imageData); // parcel.readByteArray(imageData);
} }
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {

View File

@@ -20,10 +20,11 @@
package com.kunzisoft.keepass.database.element.node package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.utils.UnsignedLong
interface NodeKDBXInterface : NodeTimeInterface { interface NodeKDBXInterface : NodeTimeInterface {
var usageCount: Long var usageCount: UnsignedLong
var locationChanged: DateInstant var locationChanged: DateInstant

View File

@@ -19,6 +19,8 @@
*/ */
package com.kunzisoft.keepass.database.file package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.utils.UnsignedInt
abstract class DatabaseHeader { abstract class DatabaseHeader {
/** /**
@@ -32,7 +34,7 @@ abstract class DatabaseHeader {
var encryptionIV = ByteArray(16) var encryptionIV = ByteArray(16)
companion object { companion object {
const val PWM_DBSIG_1 = -0x655d26fd val PWM_DBSIG_1 = UnsignedInt(-0x655d26fd)
} }
} }

View File

@@ -21,9 +21,9 @@
package com.kunzisoft.keepass.database.file package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.stream.bytes4ToInt
import com.kunzisoft.keepass.stream.readBytesLength import com.kunzisoft.keepass.stream.readBytesLength
import com.kunzisoft.keepass.stream.readBytes4ToInt import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -34,15 +34,15 @@ class DatabaseHeaderKDB : DatabaseHeader() {
*/ */
var transformSeed = ByteArray(32) var transformSeed = ByteArray(32)
var signature1: Int = 0 // = PWM_DBSIG_1 var signature1 = UnsignedInt(0) // = PWM_DBSIG_1
var signature2: Int = 0 // = DBSIG_2 var signature2 = UnsignedInt(0) // = DBSIG_2
var flags: Int = 0 var flags= UnsignedInt(0)
var version: Int = 0 var version= UnsignedInt(0)
/** Number of groups in the database */ /** Number of groups in the database */
var numGroups: Int = 0 var numGroups = UnsignedInt(0)
/** Number of entries in the database */ /** Number of entries in the database */
var numEntries: Int = 0 var numEntries = UnsignedInt(0)
/** /**
* SHA-256 hash of the database, used for integrity check * SHA-256 hash of the database, used for integrity check
@@ -50,28 +50,24 @@ class DatabaseHeaderKDB : DatabaseHeader() {
var contentsHash = ByteArray(32) var contentsHash = ByteArray(32)
// As UInt // As UInt
var numKeyEncRounds: Int = 0 var numKeyEncRounds = UnsignedInt(0)
/** /**
* Parse given buf, as read from file. * Parse given buf, as read from file.
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun loadFromFile(inputStream: InputStream) { fun loadFromFile(inputStream: InputStream) {
signature1 = inputStream.readBytes4ToInt() // 4 bytes signature1 = inputStream.readBytes4ToUInt() // 4 bytes
signature2 = inputStream.readBytes4ToInt() // 4 bytes signature2 = inputStream.readBytes4ToUInt() // 4 bytes
flags = inputStream.readBytes4ToInt() // 4 bytes flags = inputStream.readBytes4ToUInt() // 4 bytes
version = inputStream.readBytes4ToInt() // 4 bytes version = inputStream.readBytes4ToUInt() // 4 bytes
masterSeed = inputStream.readBytesLength(16) // 16 bytes masterSeed = inputStream.readBytesLength(16) // 16 bytes
encryptionIV = inputStream.readBytesLength(16) // 16 bytes encryptionIV = inputStream.readBytesLength(16) // 16 bytes
numGroups = inputStream.readBytes4ToInt() // 4 bytes numGroups = inputStream.readBytes4ToUInt() // 4 bytes
numEntries = inputStream.readBytes4ToInt() // 4 bytes numEntries = inputStream.readBytes4ToUInt() // 4 bytes
contentsHash = inputStream.readBytesLength(32) // 32 bytes contentsHash = inputStream.readBytesLength(32) // 32 bytes
transformSeed = inputStream.readBytesLength(32) // 32 bytes transformSeed = inputStream.readBytesLength(32) // 32 bytes
numKeyEncRounds = inputStream.readBytes4ToInt() numKeyEncRounds = inputStream.readBytes4ToUInt()
if (numKeyEncRounds < 0) {
// TODO: Really treat this like an unsigned integer
throw IOException("Does not support more than " + Integer.MAX_VALUE + " rounds.")
}
} }
init { init {
@@ -88,24 +84,24 @@ class DatabaseHeaderKDB : DatabaseHeader() {
companion object { companion object {
// DB sig from KeePass 1.03 // DB sig from KeePass 1.03
const val DBSIG_2 = -0x4ab4049b val DBSIG_2 = UnsignedInt(-0x4ab4049b)
// DB sig from KeePass 1.03 // DB sig from KeePass 1.03
const val DBVER_DW = 0x00030003 val DBVER_DW = UnsignedInt(0x00030003)
const val FLAG_SHA2 = 1 val FLAG_SHA2 = UnsignedInt(1)
const val FLAG_RIJNDAEL = 2 val FLAG_RIJNDAEL = UnsignedInt(2)
const val FLAG_ARCFOUR = 4 val FLAG_ARCFOUR = UnsignedInt(4)
const val FLAG_TWOFISH = 8 val FLAG_TWOFISH = UnsignedInt(8)
/** Size of byte buffer needed to hold this struct. */ /** Size of byte buffer needed to hold this struct. */
const val BUF_SIZE = 124 const val BUF_SIZE = 124
fun matchesHeader(sig1: Int, sig2: Int): Boolean { fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1 == PWM_DBSIG_1 && sig2 == DBSIG_2 return sig1.toInt() == PWM_DBSIG_1.toInt() && sig2.toInt() == DBSIG_2.toInt()
} }
fun compatibleHeaders(one: Int, two: Int): Boolean { fun compatibleHeaders(one: UnsignedInt, two: UnsignedInt): Boolean {
return one and -0x100 == two and -0x100 return one.toInt() and -0x100 == two.toInt() and -0x100
} }
} }

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.
* *
@@ -31,6 +31,8 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.exception.VersionDatabaseException import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -45,14 +47,16 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
var innerRandomStreamKey: ByteArray = ByteArray(32) var innerRandomStreamKey: ByteArray = ByteArray(32)
var streamStartBytes: ByteArray = ByteArray(32) var streamStartBytes: ByteArray = ByteArray(32)
var innerRandomStream: CrsAlgorithm? = null var innerRandomStream: CrsAlgorithm? = null
var version: Long = 0 var version: UnsignedInt = UnsignedInt(0)
// version < FILE_VERSION_32_4) // version < FILE_VERSION_32_4)
var transformSeed: ByteArray? var transformSeed: ByteArray?
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED) get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
private set(seed) { private set(seed) {
assignAesKdfEngineIfNotExists() assignAesKdfEngineIfNotExists()
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, seed) seed?.let {
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, it)
}
} }
object PwDbHeaderV4Fields { object PwDbHeaderV4Fields {
@@ -103,7 +107,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
} }
} }
private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): Long { private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): UnsignedInt {
// https://keepass.info/help/kb/kdbx_4.html // https://keepass.info/help/kb/kdbx_4.html
// Return v4 if AES is not use // Return v4 if AES is not use
@@ -147,8 +151,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
val digestInputStream = DigestInputStream(copyInputStream, messageDigest) val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream) val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
val sig1 = littleEndianDataInputStream.readInt() val sig1 = littleEndianDataInputStream.readUInt()
val sig2 = littleEndianDataInputStream.readInt() val sig2 = littleEndianDataInputStream.readUInt()
if (!matchesHeader(sig1, sig2)) { if (!matchesHeader(sig1, sig2)) {
throw VersionDatabaseException() throw VersionDatabaseException()
@@ -172,10 +176,10 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean { private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean {
val fieldID = dis.read().toByte() val fieldID = dis.read().toByte()
val fieldSize: Int = if (version < FILE_VERSION_32_4) { val fieldSize: Int = if (version.toLong() < FILE_VERSION_32_4.toLong()) {
dis.readUShort() dis.readUShort()
} else { } else {
dis.readInt() dis.readUInt().toInt()
} }
var fieldData: ByteArray? = null var fieldData: ByteArray? = null
@@ -198,20 +202,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
PwDbHeaderV4Fields.TransformSeed -> if (version < FILE_VERSION_32_4) PwDbHeaderV4Fields.TransformSeed -> if (version.toLong() < FILE_VERSION_32_4.toLong())
transformSeed = fieldData transformSeed = fieldData
PwDbHeaderV4Fields.TransformRounds -> if (version < FILE_VERSION_32_4) PwDbHeaderV4Fields.TransformRounds -> if (version.toLong() < FILE_VERSION_32_4.toLong())
setTransformRound(fieldData) setTransformRound(fieldData)
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version < FILE_VERSION_32_4) PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.toLong() < FILE_VERSION_32_4.toLong())
innerRandomStreamKey = fieldData innerRandomStreamKey = fieldData
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version < FILE_VERSION_32_4) PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.toLong() < FILE_VERSION_32_4.toLong())
setRandomStreamID(fieldData) setRandomStreamID(fieldData)
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData) PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
@@ -256,8 +260,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid compression flags.") throw IOException("Invalid compression flags.")
} }
val flag = bytes4ToInt(pbFlags) val flag = bytes4ToUInt(pbFlags)
if (flag < 0 || flag >= CompressionAlgorithm.values().size) { if (flag.toLong() < 0 || flag.toLong() >= CompressionAlgorithm.values().size) {
throw IOException("Unrecognized compression flag.") throw IOException("Unrecognized compression flag.")
} }
@@ -272,8 +276,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid stream id.") throw IOException("Invalid stream id.")
} }
val id = bytes4ToInt(streamID) val id = bytes4ToUInt(streamID)
if (id < 0 || id >= CrsAlgorithm.values().size) { if (id.toInt() < 0 || id.toInt() >= CrsAlgorithm.values().size) {
throw IOException("Invalid stream id.") throw IOException("Invalid stream id.")
} }
@@ -287,43 +291,42 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
* @param version Database version * @param version Database version
* @return true if it's a supported version * @return true if it's a supported version
*/ */
private fun validVersion(version: Long): Boolean { private fun validVersion(version: UnsignedInt): Boolean {
return version and FILE_VERSION_CRITICAL_MASK <= FILE_VERSION_32_4 and FILE_VERSION_CRITICAL_MASK return version.toInt() and FILE_VERSION_CRITICAL_MASK.toInt() <=
FILE_VERSION_32_4.toInt() and FILE_VERSION_CRITICAL_MASK.toInt()
} }
companion object { companion object {
var ULONG_MAX_VALUE: Long = -1 val DBSIG_PRE2 = UnsignedInt(-0x4ab4049a)
val DBSIG_2 = UnsignedInt(-0x4ab40499)
const val DBSIG_PRE2 = -0x4ab4049a private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000)
const val DBSIG_2 = -0x4ab40499 val FILE_VERSION_32_3 = UnsignedInt(0x00030001)
val FILE_VERSION_32_4 = UnsignedInt(0x00040000)
private const val FILE_VERSION_CRITICAL_MASK: Long = -0x10000 fun getCompressionFromFlag(flag: UnsignedInt): CompressionAlgorithm? {
const val FILE_VERSION_32_3: Long = 0x00030001 return when (flag.toInt()) {
const val FILE_VERSION_32_4: Long = 0x00040000
fun getCompressionFromFlag(flag: Int): CompressionAlgorithm? {
return when (flag) {
0 -> CompressionAlgorithm.None 0 -> CompressionAlgorithm.None
1 -> CompressionAlgorithm.GZip 1 -> CompressionAlgorithm.GZip
else -> null else -> null
} }
} }
fun getFlagFromCompression(compression: CompressionAlgorithm): Int { fun getFlagFromCompression(compression: CompressionAlgorithm): UnsignedInt {
return when (compression) { return when (compression) {
CompressionAlgorithm.GZip -> 1 CompressionAlgorithm.GZip -> UnsignedInt(1)
else -> 0 else -> UnsignedInt(0)
} }
} }
fun matchesHeader(sig1: Int, sig2: Int): Boolean { fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2) return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
} }
@Throws(IOException::class) @Throws(IOException::class)
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray { fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
val blockKey = HmacBlockStream.getHmacKey64(key, ULONG_MAX_VALUE) val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.MAX_VALUE)
val hmac: Mac val hmac: Mac
try { try {

View File

@@ -90,10 +90,10 @@ class DatabaseInputKDB(cacheDirectory: File,
// Select algorithm // Select algorithm
when { when {
header.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> { header.flags.toInt() and DatabaseHeaderKDB.FLAG_RIJNDAEL.toInt() != 0 -> {
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael
} }
header.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> { header.flags.toInt() and DatabaseHeaderKDB.FLAG_TWOFISH.toInt() != 0 -> {
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish
} }
else -> throw InvalidAlgorithmDatabaseException() else -> throw InvalidAlgorithmDatabaseException()
@@ -150,26 +150,6 @@ class DatabaseInputKDB(cacheDirectory: File,
) )
) )
/* TODO checksum
// Add a mark to the content start
if (!cipherInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
cipherInputStream.mark(cipherInputStream.available() +1)
// Consume all data to get the digest
var numberRead = 0
while (numberRead > -1) {
numberRead = cipherInputStream.read(ByteArray(1024))
}
// Check sum
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
throw InvalidCredentialsDatabaseException()
}
// Back to the content start
cipherInputStream.reset()
*/
// New manual root because KDB contains multiple root groups (here available with getRootGroups()) // New manual root because KDB contains multiple root groups (here available with getRootGroups())
val newRoot = mDatabaseToOpen.createGroup() val newRoot = mDatabaseToOpen.createGroup()
newRoot.level = -1 newRoot.level = -1
@@ -180,8 +160,8 @@ class DatabaseInputKDB(cacheDirectory: File,
var newEntry: EntryKDB? = null var newEntry: EntryKDB? = null
var currentGroupNumber = 0 var currentGroupNumber = 0
var currentEntryNumber = 0 var currentEntryNumber = 0
while (currentGroupNumber < header.numGroups while (currentGroupNumber < header.numGroups.toLong()
|| currentEntryNumber < header.numEntries) { || currentEntryNumber < header.numEntries.toLong()) {
val fieldType = cipherInputStream.readBytes2ToUShort() val fieldType = cipherInputStream.readBytes2ToUShort()
val fieldSize = cipherInputStream.readBytes4ToUInt().toInt() val fieldSize = cipherInputStream.readBytes4ToUInt().toInt()
@@ -195,7 +175,7 @@ class DatabaseInputKDB(cacheDirectory: File,
when (fieldSize) { when (fieldSize) {
4 -> { 4 -> {
newGroup = mDatabaseToOpen.createGroup().apply { newGroup = mDatabaseToOpen.createGroup().apply {
setGroupId(cipherInputStream.readBytes4ToInt()) setGroupId(cipherInputStream.readBytes4ToUInt().toInt())
} }
} }
16 -> { 16 -> {
@@ -214,7 +194,7 @@ class DatabaseInputKDB(cacheDirectory: File,
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
val groupKDB = mDatabaseToOpen.createGroup() val groupKDB = mDatabaseToOpen.createGroup()
groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToInt()) groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToUInt().toInt())
entry.parent = groupKDB entry.parent = groupKDB
} }
} }
@@ -223,7 +203,7 @@ class DatabaseInputKDB(cacheDirectory: File,
group.creationTime = cipherInputStream.readBytes5ToDate() group.creationTime = cipherInputStream.readBytes5ToDate()
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
var iconId = cipherInputStream.readBytes4ToInt() var iconId = cipherInputStream.readBytes4ToUInt().toInt()
// Clean up after bug that set icon ids to -1 // Clean up after bug that set icon ids to -1
if (iconId == -1) { if (iconId == -1) {
iconId = 0 iconId = 0
@@ -257,7 +237,7 @@ class DatabaseInputKDB(cacheDirectory: File,
} }
0x0007 -> { 0x0007 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToInt()) group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToUInt().toInt())
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.password = cipherInputStream.readBytesToString(fieldSize,false) entry.password = cipherInputStream.readBytesToString(fieldSize,false)
@@ -273,7 +253,7 @@ class DatabaseInputKDB(cacheDirectory: File,
} }
0x0009 -> { 0x0009 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.flags = cipherInputStream.readBytes4ToInt() group.groupFlags = cipherInputStream.readBytes4ToUInt().toInt()
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.creationTime = cipherInputStream.readBytes5ToDate() entry.creationTime = cipherInputStream.readBytes5ToDate()

View File

@@ -43,7 +43,9 @@ 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.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import org.spongycastle.crypto.StreamCipher import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import org.bouncycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory import org.xmlpull.v1.XmlPullParserFactory
@@ -130,7 +132,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
} }
val isPlain: InputStream val isPlain: InputStream
if (mDatabase.kdbxVersion < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
val decrypted = attachCipherStream(databaseInputStream, cipher) val decrypted = attachCipherStream(databaseInputStream, cipher)
val dataDecrypted = LittleEndianDataInputStream(decrypted) val dataDecrypted = LittleEndianDataInputStream(decrypted)
@@ -178,7 +180,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
else -> isPlain else -> isPlain
} }
if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
loadInnerHeader(inputStreamXml, header) loadInnerHeader(inputStreamXml, header)
} }
@@ -226,19 +228,24 @@ class DatabaseInputKDBX(cacheDirectory: File,
header: DatabaseHeaderKDBX): Boolean { header: DatabaseHeaderKDBX): Boolean {
val fieldId = dataInputStream.read().toByte() val fieldId = dataInputStream.read().toByte()
val size = dataInputStream.readInt() val size = dataInputStream.readUInt().toInt()
if (size < 0) throw IOException("Corrupted file") if (size < 0) throw IOException("Corrupted file")
var data = ByteArray(0)
if (size > 0) {
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
data = dataInputStream.readBytes(size)
}
var result = true
when (fieldId) { when (fieldId) {
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> {
return false result = false
} }
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> {
val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0)
header.setRandomStreamID(data) header.setRandomStreamID(data)
} }
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> {
val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0)
header.innerRandomStreamKey = data header.innerRandomStreamKey = data
} }
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
@@ -255,12 +262,9 @@ class DatabaseInputKDBX(cacheDirectory: File,
val protectedBinary = BinaryAttachment(file, protectedFlag) val protectedBinary = BinaryAttachment(file, protectedFlag)
mDatabase.binaryPool.add(protectedBinary) mDatabase.binaryPool.add(protectedBinary)
} }
else -> {
return false
}
} }
return true return result
} }
private enum class KdbContext { private enum class KdbContext {
@@ -488,7 +492,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
} else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) {
ctxGroup?.notes = readString(xpp) ctxGroup?.notes = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt()) ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toInt())
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp)) ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
@@ -542,7 +546,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) { KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp)) ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt()) ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toInt())
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp)) ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) {
@@ -598,7 +602,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp) name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp)
name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp) name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp)
name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false) name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false)
name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, 0) name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, UnsignedLong(0))
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp) name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp)
else -> readUnknown(xpp) else -> readUnknown(xpp)
} }
@@ -621,7 +625,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
KdbContext.EntryAutoType -> if (name.equals(DatabaseKDBXXML.ElemAutoTypeEnabled, ignoreCase = true)) { KdbContext.EntryAutoType -> if (name.equals(DatabaseKDBXXML.ElemAutoTypeEnabled, ignoreCase = true)) {
ctxEntry?.autoType?.enabled = readBool(xpp, true) ctxEntry?.autoType?.enabled = readBool(xpp, true)
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeObfuscation, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemAutoTypeObfuscation, ignoreCase = true)) {
ctxEntry?.autoType?.obfuscationOptions = readUInt(xpp, 0) ctxEntry?.autoType?.obfuscationOptions = readUInt(xpp, UnsignedInt(0))
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, ignoreCase = true)) {
ctxEntry?.autoType?.defaultSequence = readString(xpp) ctxEntry?.autoType?.defaultSequence = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) {
@@ -815,7 +819,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
val sDate = readString(xpp) val sDate = readString(xpp)
var utcDate: Date? = null var utcDate: Date? = null
if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
var buf = Base64.decode(sDate, BASE_64_FLAG) var buf = Base64.decode(sDate, BASE_64_FLAG)
if (buf.size != 8) { if (buf.size != 8) {
val buf8 = ByteArray(8) val buf8 = ByteArray(8)
@@ -887,48 +891,39 @@ class DatabaseInputKDBX(cacheDirectory: File,
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun readInt(xpp: XmlPullParser, def: Int): Int { private fun readInt(xpp: XmlPullParser, default: Int): Int {
val str = readString(xpp)
return try { return try {
Integer.parseInt(str) readString(xpp).toInt()
} catch (e: NumberFormatException) { } catch (e: Exception) {
def default
} }
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun readUInt(xpp: XmlPullParser, uDefault: Long): Long { private fun readUInt(xpp: XmlPullParser, default: UnsignedInt): UnsignedInt {
val u: Long = readULong(xpp, uDefault)
if (u < 0 || u > MAX_UINT) {
throw NumberFormatException("Outside of the uint size")
}
return u
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readLong(xpp: XmlPullParser, def: Long): Long {
val str = readString(xpp)
return try { return try {
java.lang.Long.parseLong(str) UnsignedInt(readString(xpp).toInt())
} catch (e: NumberFormatException) { } catch (e: Exception) {
def default
} }
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun readULong(xpp: XmlPullParser, uDefault: Long): Long { private fun readLong(xpp: XmlPullParser, default: Long): Long {
var u = readLong(xpp, uDefault) return try {
readString(xpp).toLong()
if (u < 0) { } catch (e: Exception) {
u = uDefault default
} }
}
return u @Throws(IOException::class, XmlPullParserException::class)
private fun readULong(xpp: XmlPullParser, default: UnsignedLong): UnsignedLong {
return try {
UnsignedLong(readString(xpp).toLong())
} catch (e: Exception) {
default
}
} }
@Throws(XmlPullParserException::class, IOException::class) @Throws(XmlPullParserException::class, IOException::class)
@@ -1050,7 +1045,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
companion object { companion object {
private const val DEFAULT_HISTORY_DAYS: Long = 365 private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
@Throws(XmlPullParserException::class) @Throws(XmlPullParserException::class)
private fun createPullParser(readerStream: InputStream): XmlPullParser { private fun createPullParser(readerStream: InputStream): XmlPullParser {
@@ -1062,8 +1057,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
return xpp return xpp
} }
private const val MAX_UINT = 4294967296L // 2^32
} }
} }

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.intTo4Bytes import com.kunzisoft.keepass.stream.uIntTo4Bytes
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@@ -29,14 +29,14 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
@Throws(IOException::class) @Throws(IOException::class)
fun outputStart() { fun outputStart() {
mOutputStream.write(intTo4Bytes(mHeader.signature1)) mOutputStream.write(uIntTo4Bytes(mHeader.signature1))
mOutputStream.write(intTo4Bytes(mHeader.signature2)) mOutputStream.write(uIntTo4Bytes(mHeader.signature2))
mOutputStream.write(intTo4Bytes(mHeader.flags)) mOutputStream.write(uIntTo4Bytes(mHeader.flags))
mOutputStream.write(intTo4Bytes(mHeader.version)) mOutputStream.write(uIntTo4Bytes(mHeader.version))
mOutputStream.write(mHeader.masterSeed) mOutputStream.write(mHeader.masterSeed)
mOutputStream.write(mHeader.encryptionIV) mOutputStream.write(mHeader.encryptionIV)
mOutputStream.write(intTo4Bytes(mHeader.numGroups)) mOutputStream.write(uIntTo4Bytes(mHeader.numGroups))
mOutputStream.write(intTo4Bytes(mHeader.numEntries)) mOutputStream.write(uIntTo4Bytes(mHeader.numEntries))
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -47,7 +47,7 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
@Throws(IOException::class) @Throws(IOException::class)
fun outputEnd() { fun outputEnd() {
mOutputStream.write(mHeader.transformSeed) mOutputStream.write(mHeader.transformSeed)
mOutputStream.write(intTo4Bytes(mHeader.numKeyEncRounds)) mOutputStream.write(uIntTo4Bytes(mHeader.numKeyEncRounds))
} }
@Throws(IOException::class) @Throws(IOException::class)

View File

@@ -24,8 +24,8 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.VariantDictionary
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
@@ -66,7 +66,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
val hmac: Mac val hmac: Mac
try { try {
hmac = Mac.getInstance("HmacSHA256") hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, ULONG_MAX_VALUE), "HmacSHA256") val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.MAX_VALUE), "HmacSHA256")
hmac.init(signingKey) hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException(e) throw DatabaseOutputException(e)
@@ -82,15 +82,15 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class) @Throws(IOException::class)
fun output() { fun output() {
los.writeUInt(DatabaseHeader.PWM_DBSIG_1.toLong()) los.writeUInt(DatabaseHeader.PWM_DBSIG_1)
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2.toLong()) los.writeUInt(DatabaseHeaderKDBX.DBSIG_2)
los.writeUInt(header.version) los.writeUInt(header.version)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, intTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm))) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else { } else {
@@ -101,10 +101,10 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
} }
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, intTo4Bytes(header.innerRandomStream!!.id)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
} }
if (databaseKDBX.containsPublicCustomData()) { if (databaseKDBX.containsPublicCustomData()) {
@@ -136,7 +136,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class) @Throws(IOException::class)
private fun writeHeaderFieldSize(size: Int) { private fun writeHeaderFieldSize(size: Int) {
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
los.writeUShort(size) los.writeUShort(size)
} else { } else {
los.writeInt(size) los.writeInt(size)

View File

@@ -40,7 +40,7 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
dataOutputStream.writeInt(4) dataOutputStream.writeInt(4)
if (header.innerRandomStream == null) if (header.innerRandomStream == null)
throw IOException("Can't write innerRandomStream") throw IOException("Can't write innerRandomStream")
dataOutputStream.writeInt(header.innerRandomStream!!.id) dataOutputStream.writeInt(header.innerRandomStream!!.id.toInt())
val streamKeySize = header.innerRandomStreamKey.size val streamKeySize = header.innerRandomStreamKey.size
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt()) dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt())

View File

@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
@@ -117,18 +118,18 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
when { when {
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> { mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
header.flags = header.flags or DatabaseHeaderKDB.FLAG_RIJNDAEL header.flags = UnsignedInt(header.flags.toInt() or DatabaseHeaderKDB.FLAG_RIJNDAEL.toInt())
} }
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> { mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
header.flags = header.flags or DatabaseHeaderKDB.FLAG_TWOFISH header.flags = UnsignedInt(header.flags.toInt() or DatabaseHeaderKDB.FLAG_TWOFISH.toInt())
} }
else -> throw DatabaseOutputException("Unsupported algorithm.") else -> throw DatabaseOutputException("Unsupported algorithm.")
} }
header.version = DatabaseHeaderKDB.DBVER_DW header.version = DatabaseHeaderKDB.DBVER_DW
header.numGroups = mDatabaseKDB.numberOfGroups() header.numGroups = UnsignedInt(mDatabaseKDB.numberOfGroups())
header.numEntries = mDatabaseKDB.numberOfEntries() header.numEntries = UnsignedInt(mDatabaseKDB.numberOfEntries())
header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt() // TODO Signed Long - Unsigned Int header.numKeyEncRounds = UnsignedInt.fromLong(mDatabaseKDB.numberKeyEncryptionRounds)
setIVs(header) setIVs(header)
@@ -279,6 +280,5 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
if (data != null) { if (data != null) {
los.write(data) los.write(data)
} }
} }
} }

View File

@@ -47,8 +47,8 @@ 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 org.bouncycastle.crypto.StreamCipher
import org.joda.time.DateTime import org.joda.time.DateTime
import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlSerializer import org.xmlpull.v1.XmlSerializer
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@@ -85,7 +85,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
header = outputHeader(mOS) header = outputHeader(mOS)
val osPlain: OutputStream val osPlain: OutputStream
osPlain = if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { osPlain = if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
val cos = attachStreamEncryptor(header!!, mOS) val cos = attachStreamEncryptor(header!!, mOS)
cos.write(header!!.streamStartBytes) cos.write(header!!.streamStartBytes)
@@ -105,7 +105,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
else -> osPlain else -> osPlain
} }
if (header!!.version >= DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header!!.version.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml) val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml)
ihOut.output() ihOut.output()
} }
@@ -209,7 +209,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeObject(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged.date) writeObject(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged.date)
writeObject(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true) writeObject(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true)
writeObject(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged.date) writeObject(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged.date)
writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays) writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays.toLong())
writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color) writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date) writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date)
writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays) writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
@@ -230,7 +230,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID) writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta // Seem to work properly if always in meta
if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong())
writeMetaBinaries() writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData) writeCustomData(mDatabaseKDBX.customData)
@@ -274,7 +274,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
Log.e(TAG, "Unable to retrieve header", unknownKDF) Log.e(TAG, "Unable to retrieve header", unknownKDF)
} }
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
header.innerRandomStream = CrsAlgorithm.Salsa20 header.innerRandomStream = CrsAlgorithm.Salsa20
header.innerRandomStreamKey = ByteArray(32) header.innerRandomStreamKey = ByteArray(32)
} else { } else {
@@ -288,7 +288,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
throw DatabaseOutputException("Invalid random cipher") throw DatabaseOutputException("Invalid random cipher")
} }
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
random.nextBytes(header.streamStartBytes) random.nextBytes(header.streamStartBytes)
} }
@@ -385,7 +385,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Date) { private fun writeObject(name: String, value: Date) {
if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value)) writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
} else { } else {
val dt = DateTime(value) val dt = DateTime(value)
@@ -489,7 +489,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemAutoType) xml.startTag(null, DatabaseKDBXXML.ElemAutoType)
writeObject(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled) writeObject(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled)
writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions) writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions.toLong())
if (autoType.defaultSequence.isNotEmpty()) { if (autoType.defaultSequence.isNotEmpty()) {
writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true) writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
@@ -629,7 +629,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeObject(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime.date) writeObject(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime.date)
writeObject(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime.date) writeObject(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime.date)
writeObject(DatabaseKDBXXML.ElemExpires, node.expires) writeObject(DatabaseKDBXXML.ElemExpires, node.expires)
writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount) writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount.toLong())
writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date) writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date)
xml.endTag(null, DatabaseKDBXXML.ElemTimes) xml.endTag(null, DatabaseKDBXXML.ElemTimes)

View File

@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.file.output.GroupOutputKDB.Companion.GROUPID_FIELD_SIZE import com.kunzisoft.keepass.database.file.output.GroupOutputKDB.Companion.GROUPID_FIELD_SIZE
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.nio.charset.Charset import java.nio.charset.Charset
@@ -54,12 +55,12 @@ class EntryOutputKDB
// Group ID // Group ID
mOutputStream.write(GROUPID_FIELD_TYPE) mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(GROUPID_FIELD_SIZE) mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mEntry.parent!!.id)) mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.parent!!.id)))
// Image ID // Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE) mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE) mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mEntry.icon.iconId)) mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.icon.iconId)))
// Title // Title
//byte[] title = mEntry.title.getBytes("UTF-8"); //byte[] title = mEntry.title.getBytes("UTF-8");
@@ -101,14 +102,9 @@ class EntryOutputKDB
// Binary // Binary
mOutputStream.write(BINARY_DATA_FIELD_TYPE) mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.binaryData val binaryData = mEntry.binaryData
val binaryDataLength = binaryData?.length() ?: 0 val binaryDataLength = binaryData?.length() ?: 0L
val binaryDataLengthRightSize = if (binaryDataLength <= Int.MAX_VALUE) {
binaryDataLength.toInt()
} else {
0 // TODO if length > UInt.maxvalue show exception
}
// Write data length // Write data length
mOutputStream.write(intTo4Bytes(binaryDataLengthRightSize)) mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromLong(binaryDataLength)))
// Write data // Write data
if (binaryDataLength > 0) { if (binaryDataLength > 0) {
binaryData?.getInputDataStream().use { inputStream -> binaryData?.getInputDataStream().use { inputStream ->
@@ -140,7 +136,7 @@ class EntryOutputKDB
private fun writePassword(str: String, os: OutputStream): Int { private fun writePassword(str: String, os: OutputStream): Int {
val initial = str.toByteArray(Charset.forName("UTF-8")) val initial = str.toByteArray(Charset.forName("UTF-8"))
val length = initial.size + 1 val length = initial.size + 1
os.write(intTo4Bytes(length)) os.write(uIntTo4Bytes(UnsignedInt(length)))
os.write(initial) os.write(initial)
os.write(0x00) os.write(0x00)
return length return length
@@ -164,13 +160,13 @@ class EntryOutputKDB
val BINARY_DATA_FIELD_TYPE:ByteArray = uShortTo2Bytes(14) val BINARY_DATA_FIELD_TYPE:ByteArray = uShortTo2Bytes(14)
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF) val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val LONG_FOUR:ByteArray = intTo4Bytes(4) val LONG_FOUR:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val UUID_FIELD_SIZE:ByteArray = intTo4Bytes(16) val UUID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(16))
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5) val DATE_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(5))
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4) val IMAGEID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(4) val LEVEL_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4) val FLAGS_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0) val ZERO_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(0))
val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00) val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)
} }
} }

View File

@@ -21,9 +21,10 @@ package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.stream.dateTo5Bytes import com.kunzisoft.keepass.stream.dateTo5Bytes
import com.kunzisoft.keepass.stream.intTo4Bytes import com.kunzisoft.keepass.stream.uIntTo4Bytes
import com.kunzisoft.keepass.stream.uShortTo2Bytes import com.kunzisoft.keepass.stream.uShortTo2Bytes
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@@ -39,7 +40,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Group ID // Group ID
mOutputStream.write(GROUPID_FIELD_TYPE) mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(GROUPID_FIELD_SIZE) mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mGroup.id)) mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.id)))
// Name // Name
mOutputStream.write(NAME_FIELD_TYPE) mOutputStream.write(NAME_FIELD_TYPE)
@@ -68,7 +69,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Image ID // Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE) mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE) mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mGroup.icon.iconId)) mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.icon.iconId)))
// Level // Level
mOutputStream.write(LEVEL_FIELD_TYPE) mOutputStream.write(LEVEL_FIELD_TYPE)
@@ -78,7 +79,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Flags // Flags
mOutputStream.write(FLAGS_FIELD_TYPE) mOutputStream.write(FLAGS_FIELD_TYPE)
mOutputStream.write(FLAGS_FIELD_SIZE) mOutputStream.write(FLAGS_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mGroup.flags)) mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.groupFlags)))
// End // End
mOutputStream.write(END_FIELD_TYPE) mOutputStream.write(END_FIELD_TYPE)
@@ -98,11 +99,11 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
val FLAGS_FIELD_TYPE:ByteArray = uShortTo2Bytes(9) val FLAGS_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF) val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val GROUPID_FIELD_SIZE:ByteArray = intTo4Bytes(4) val GROUPID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5) val DATE_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(5))
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4) val IMAGEID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(2) val LEVEL_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(2))
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4) val FLAGS_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0) val ZERO_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(0))
} }
} }

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.
* *
@@ -22,11 +22,10 @@ package com.kunzisoft.keepass.database.search
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
import com.kunzisoft.keepass.utils.StringUtil
import java.util.Locale class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters,
private val mListStorage: MutableList<EntryKDBX>)
class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParametersKDBX, private val mListStorage: MutableList<EntryKDBX>) : NodeHandler<EntryKDBX>() { : NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean { override fun operate(node: EntryKDBX): Boolean {
@@ -35,32 +34,17 @@ class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters
return true return true
} }
var term = mSearchParametersKDBX.searchString if (searchStrings(node)) {
if (mSearchParametersKDBX.ignoreCase) {
term = term.toLowerCase()
}
if (searchStrings(node, term)) {
mListStorage.add(node) mListStorage.add(node)
return true return true
} }
if (mSearchParametersKDBX.searchInGroupNames) { if (searchInGroupNames(node)) {
val parent = node.parent mListStorage.add(node)
if (parent != null) { return true
var groupName = parent.title
if (mSearchParametersKDBX.ignoreCase) {
groupName = groupName.toLowerCase()
}
if (groupName.contains(term)) {
mListStorage.add(node)
return true
}
}
} }
if (searchID(node)) { if (searchInUUID(node)) {
mListStorage.add(node) mListStorage.add(node)
return true return true
} }
@@ -68,25 +52,33 @@ class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters
return true return true
} }
private fun searchID(entry: EntryKDBX): Boolean { private fun searchInGroupNames(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInUUIDs) { if (mSearchParametersKDBX.searchInGroupNames) {
val hex = UuidUtil.toHexString(entry.id) val parent = entry.parent
return StringUtil.indexOfIgnoreCase(hex, mSearchParametersKDBX.searchString, Locale.ENGLISH) >= 0 if (parent != null) {
return parent.title
.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)
}
} }
return false return false
} }
private fun searchStrings(entry: EntryKDBX, term: String): Boolean { private fun searchInUUID(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInUUIDs) {
return UuidUtil.toHexString(entry.id)
.contains(mSearchParametersKDBX.searchString, true)
}
return false
}
private fun searchStrings(entry: EntryKDBX): Boolean {
val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX) val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX)
while (iterator.hasNext()) { while (iterator.hasNext()) {
var str = iterator.next() val stringValue = iterator.next()
if (str.isNotEmpty()) { if (stringValue.isNotEmpty()) {
if (mSearchParametersKDBX.ignoreCase) { if (stringValue.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)) {
str = str.toLowerCase()
}
if (str.contains(term)) {
return true return true
} }
} }

View File

@@ -23,10 +23,8 @@ import com.kunzisoft.keepass.database.action.node.NodeHandler
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
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIterator
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
import java.util.*
class SearchHelper(private val isOmitBackup: Boolean) { class SearchHelper(private val isOmitBackup: Boolean) {
@@ -36,22 +34,22 @@ class SearchHelper(private val isOmitBackup: Boolean) {
private var incrementEntry = 0 private var incrementEntry = 0
fun search(database: Database, qStr: String, max: Int): Group? { fun createVirtualGroupWithSearchResult(database: Database,
searchQuery: String,
searchParameters: SearchParameters,
max: Int): Group? {
val searchGroup = database.createGroup() val searchGroup = database.createGroup()
searchGroup?.title = "\"" + qStr + "\"" searchGroup?.title = "\"" + searchQuery + "\""
// Search all entries // Search all entries
val loc = Locale.getDefault()
val finalQStr = qStr.toLowerCase(loc)
incrementEntry = 0 incrementEntry = 0
database.rootGroup?.doForEachChild( database.rootGroup?.doForEachChild(
object : NodeHandler<Entry>() { object : NodeHandler<Entry>() {
override fun operate(node: Entry): Boolean { override fun operate(node: Entry): Boolean {
if (incrementEntry >= max) if (incrementEntry >= max)
return false return false
if (entryContainsString(node, finalQStr, loc)) { if (entryContainsString(node, searchQuery, searchParameters)) {
searchGroup?.addChildEntry(node) searchGroup?.addChildEntry(node)
incrementEntry++ incrementEntry++
} }
@@ -73,28 +71,29 @@ class SearchHelper(private val isOmitBackup: Boolean) {
return searchGroup return searchGroup
} }
private fun entryContainsString(entry: Entry, searchString: String, locale: Locale): Boolean { private fun entryContainsString(entry: Entry,
searchQuery: String,
searchParameters: SearchParameters): Boolean {
// Entry don't contains string if the search string is empty // Entry don't contains string if the search string is empty
if (searchString.isEmpty()) if (searchQuery.isEmpty())
return false return false
// Search all strings in the entry // Search all strings in the entry
var iterator: EntrySearchStringIterator? = null var iterator: Iterator<String>? = null
entry.entryKDB?.let { entry.entryKDB?.let {
iterator = EntrySearchStringIteratorKDB(it) iterator = EntrySearchStringIteratorKDB(it, searchParameters)
} }
entry.entryKDBX?.let { entry.entryKDBX?.let {
iterator = EntrySearchStringIteratorKDBX(it) iterator = EntrySearchStringIteratorKDBX(it, searchParameters)
} }
iterator?.let { iterator?.let {
while (it.hasNext()) { while (it.hasNext()) {
val str = it.next() val currentString = it.next()
if (str.isNotEmpty()) { if (currentString.isNotEmpty()
if (str.toLowerCase(locale).contains(searchString)) { && currentString.contains(searchQuery, true)) {
return true return true
}
} }
} }
} }

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.
* *
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.search
/** /**
* Parameters for searching strings in the database. * Parameters for searching strings in the database.
*/ */
open class SearchParameters { class SearchParameters {
var searchString: String = "" var searchString: String = ""
@@ -33,6 +33,9 @@ open class SearchParameters {
var searchInUrls = true var searchInUrls = true
var searchInGroupNames = false var searchInGroupNames = false
var searchInNotes = true var searchInNotes = true
var searchInOther = true
var searchInUUIDs = false
var searchInTags = true
var ignoreCase = true var ignoreCase = true
var ignoreExpired = false var ignoreExpired = false
var excludeExpired = false var excludeExpired = false
@@ -44,24 +47,30 @@ open class SearchParameters {
* @param source * @param source
*/ */
constructor(source: SearchParameters) { constructor(source: SearchParameters) {
regularExpression = source.regularExpression this.regularExpression = source.regularExpression
searchInTitles = source.searchInTitles this.searchInTitles = source.searchInTitles
searchInUserNames = source.searchInUserNames this.searchInUserNames = source.searchInUserNames
searchInPasswords = source.searchInPasswords this.searchInPasswords = source.searchInPasswords
searchInUrls = source.searchInUrls this.searchInUrls = source.searchInUrls
searchInGroupNames = source.searchInGroupNames this.searchInGroupNames = source.searchInGroupNames
searchInNotes = source.searchInNotes this.searchInNotes = source.searchInNotes
ignoreCase = source.ignoreCase this.searchInOther = source.searchInOther
ignoreExpired = source.ignoreExpired this.searchInUUIDs = source.searchInUUIDs
excludeExpired = source.excludeExpired this.searchInTags = source.searchInTags
this.ignoreCase = source.ignoreCase
this.ignoreExpired = source.ignoreExpired
this.excludeExpired = source.excludeExpired
} }
open fun setupNone() { fun setupNone() {
searchInTitles = false searchInTitles = false
searchInUserNames = false searchInUserNames = false
searchInPasswords = false searchInPasswords = false
searchInUrls = false searchInUrls = false
searchInGroupNames = false searchInGroupNames = false
searchInNotes = false searchInNotes = false
searchInOther = false
searchInUUIDs = false
searchInTags = false
} }
} }

View File

@@ -1,42 +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.search
class SearchParametersKDBX : SearchParameters {
var searchInOther = true
var searchInUUIDs = false
var searchInTags = true
constructor() : super()
constructor(searchParametersV4: SearchParametersKDBX) : super(searchParametersV4) {
this.searchInOther = searchParametersV4.searchInOther
this.searchInUUIDs = searchParametersV4.searchInUUIDs
this.searchInTags = searchParametersV4.searchInTags
}
override fun setupNone() {
super.setupNone()
searchInOther = false
searchInUUIDs = false
searchInTags = false
}
}

View File

@@ -1,22 +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.search.iterator
abstract class EntrySearchStringIterator : Iterator<String>

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.
* *
@@ -24,12 +24,10 @@ import com.kunzisoft.keepass.database.search.SearchParameters
import java.util.NoSuchElementException import java.util.NoSuchElementException
class EntrySearchStringIteratorKDB class EntrySearchStringIteratorKDB(
private val mEntry: EntryKDB,
@JvmOverloads private val mSearchParameters: SearchParameters)
constructor(private val mEntry: EntryKDB, : Iterator<String> {
private val mSearchParameters: SearchParameters? = SearchParameters())
: EntrySearchStringIterator() {
private var current = 0 private var current = 0
@@ -39,7 +37,7 @@ constructor(private val mEntry: EntryKDB,
title -> mEntry.title title -> mEntry.title
url -> mEntry.url url -> mEntry.url
username -> mEntry.username username -> mEntry.username
comment -> mEntry.notes notes -> mEntry.notes
else -> "" else -> ""
} }
} }
@@ -62,18 +60,13 @@ constructor(private val mEntry: EntryKDB,
} }
private fun useSearchParameters() { private fun useSearchParameters() {
if (mSearchParameters == null) {
return
}
var found = false var found = false
while (!found) { while (!found) {
found = when (current) { found = when (current) {
title -> mSearchParameters.searchInTitles title -> mSearchParameters.searchInTitles
url -> mSearchParameters.searchInUrls url -> mSearchParameters.searchInUrls
username -> mSearchParameters.searchInUserNames username -> mSearchParameters.searchInUserNames
comment -> mSearchParameters.searchInNotes notes -> mSearchParameters.searchInNotes
else -> true else -> true
} }
@@ -84,11 +77,10 @@ constructor(private val mEntry: EntryKDB,
} }
companion object { companion object {
private const val title = 0 private const val title = 0
private const val url = 1 private const val url = 1
private const val username = 2 private const val username = 2
private const val comment = 3 private const val notes = 3
private const val maxEntries = 4 private const val maxEntries = 4
} }

View File

@@ -20,25 +20,20 @@
package com.kunzisoft.keepass.database.search.iterator package com.kunzisoft.keepass.database.search.iterator
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.search.SearchParametersKDBX
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.search.SearchParameters
import java.util.* import java.util.*
import kotlin.collections.Map.Entry import kotlin.collections.Map.Entry
class EntrySearchStringIteratorKDBX : EntrySearchStringIterator { class EntrySearchStringIteratorKDBX(
entry: EntryKDBX,
private val mSearchParameters: SearchParameters)
: Iterator<String> {
private var mCurrent: String? = null private var mCurrent: String? = null
private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null
private var mSearchParametersV4: SearchParametersKDBX
constructor(entry: EntryKDBX) { init {
this.mSearchParametersV4 = SearchParametersKDBX()
mSetIterator = entry.fields.entries.iterator()
advance()
}
constructor(entry: EntryKDBX, searchParametersV4: SearchParametersKDBX) {
this.mSearchParametersV4 = searchParametersV4
mSetIterator = entry.fields.entries.iterator() mSetIterator = entry.fields.entries.iterator()
advance() advance()
} }
@@ -75,12 +70,12 @@ class EntrySearchStringIteratorKDBX : EntrySearchStringIterator {
private fun searchInField(key: String): Boolean { private fun searchInField(key: String): Boolean {
return when (key) { return when (key) {
EntryKDBX.STR_TITLE -> mSearchParametersV4.searchInTitles EntryKDBX.STR_TITLE -> mSearchParameters.searchInTitles
EntryKDBX.STR_USERNAME -> mSearchParametersV4.searchInUserNames EntryKDBX.STR_USERNAME -> mSearchParameters.searchInUserNames
EntryKDBX.STR_PASSWORD -> mSearchParametersV4.searchInPasswords EntryKDBX.STR_PASSWORD -> mSearchParameters.searchInPasswords
EntryKDBX.STR_URL -> mSearchParametersV4.searchInUrls EntryKDBX.STR_URL -> mSearchParameters.searchInUrls
EntryKDBX.STR_NOTES -> mSearchParametersV4.searchInNotes EntryKDBX.STR_NOTES -> mSearchParameters.searchInNotes
else -> mSearchParametersV4.searchInOther else -> mSearchParameters.searchInOther
} }
} }

View File

@@ -22,16 +22,16 @@ package com.kunzisoft.keepass.icons
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Bitmap import android.graphics.*
import android.graphics.BitmapFactory
import android.graphics.Color
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 androidx.core.content.res.ResourcesCompat
import androidx.core.widget.ImageViewCompat
import android.util.Log import android.util.Log
import android.widget.ImageView import android.widget.ImageView
import android.widget.RemoteViews
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.ImageViewCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
@@ -62,7 +62,7 @@ class IconDrawableFactory {
fun assignDrawableToImageView(superDrawable: SuperDrawable, imageView: ImageView?, tint: Boolean, tintColor: Int) { fun assignDrawableToImageView(superDrawable: SuperDrawable, imageView: ImageView?, tint: Boolean, tintColor: Int) {
if (imageView != null) { if (imageView != null) {
imageView.setImageDrawable(superDrawable.drawable) imageView.setImageDrawable(superDrawable.drawable)
if (!superDrawable.custom && tint) { if (superDrawable.tintable && tint) {
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor)) ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor))
} else { } else {
ImageViewCompat.setImageTintList(imageView, null) ImageViewCompat.setImageTintList(imageView, null)
@@ -70,6 +70,23 @@ class IconDrawableFactory {
} }
} }
/**
* Utility method to assign a drawable to a RemoteView and tint it
*/
fun assignDrawableToRemoteViews(superDrawable: SuperDrawable,
remoteViews: RemoteViews,
imageId: Int,
tintColor: Int = Color.BLACK) {
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)
})
}
remoteViews.setImageViewBitmap(imageId, 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
*/ */
@@ -80,7 +97,7 @@ class IconDrawableFactory {
getIconSuperDrawable(context, resId, width, tint, tintColor) getIconSuperDrawable(context, resId, width, tint, tintColor)
} }
is IconImageCustom -> { is IconImageCustom -> {
SuperDrawable(getIconDrawable(context.resources, icon), true) SuperDrawable(getIconDrawable(context.resources, icon))
} }
else -> { else -> {
SuperDrawable(PatternIcon(context.resources).blankDrawable) SuperDrawable(PatternIcon(context.resources).blankDrawable)
@@ -93,7 +110,7 @@ class IconDrawableFactory {
* , then [tint] it with [tintColor] if needed * , then [tint] it with [tintColor] if needed
*/ */
fun getIconSuperDrawable(context: Context, iconId: Int, width: Int, tint: Boolean, tintColor: Int): SuperDrawable { fun getIconSuperDrawable(context: Context, iconId: Int, width: Int, tint: Boolean, tintColor: Int): SuperDrawable {
return SuperDrawable(getIconDrawable(context.resources, iconId, width, tint, tintColor)) return SuperDrawable(getIconDrawable(context.resources, iconId, width, tint, tintColor), true)
} }
/** /**
@@ -219,7 +236,7 @@ class IconDrawableFactory {
/** /**
* Utility class to prevent a custom icon to be tint * Utility class to prevent a custom icon to be tint
*/ */
class SuperDrawable(var drawable: Drawable, var custom: Boolean = false) class SuperDrawable(var drawable: Drawable, var tintable: Boolean = false)
companion object { companion object {
@@ -231,35 +248,64 @@ class IconDrawableFactory {
/** /**
* Assign a default database icon to an ImageView and tint it with [tintColor] if needed * Assign a default database icon to an ImageView and tint it with [tintColor] if needed
*/ */
fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory, tintColor: Int = Color.WHITE) { fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory,
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack -> tintColor: Int = Color.WHITE) {
try {
iconFactory.assignDrawableToImageView( IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
iconFactory.getIconSuperDrawable(context, iconFactory.assignDrawableToImageView(
selectedIconPack.defaultIconId, iconFactory.getIconSuperDrawable(context,
width, selectedIconPack.defaultIconId,
selectedIconPack.tintable(), width,
tintColor), selectedIconPack.tintable(),
this, tintColor),
selectedIconPack.tintable(), this,
tintColor) selectedIconPack.tintable(),
tintColor)
}
} catch (e: Exception) {
Log.e(ImageView::class.java.name, "Unable to assign icon in image view", e)
} }
} }
/** /**
* Assign a database [icon] to an ImageView and tint it with [tintColor] if needed * Assign a database [icon] to an ImageView and tint it with [tintColor] if needed
*/ */
fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory, icon: IconImage, tintColor: Int = Color.WHITE) { fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory,
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack -> icon: IconImage,
tintColor: Int = Color.WHITE) {
iconFactory.assignDrawableToImageView( try {
iconFactory.getIconSuperDrawable(context, IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
icon, iconFactory.assignDrawableToImageView(
width, iconFactory.getIconSuperDrawable(context,
true, icon,
tintColor), width,
this, true,
selectedIconPack.tintable(), tintColor),
tintColor) this,
selectedIconPack.tintable(),
tintColor)
}
} catch (e: Exception) {
Log.e(ImageView::class.java.name, "Unable to assign icon in image view", e)
}
}
fun RemoteViews.assignDatabaseIcon(context: Context,
imageId: Int,
iconFactory: IconDrawableFactory,
icon: IconImage,
tintColor: Int = Color.BLACK) {
try {
iconFactory.assignDrawableToRemoteViews(
iconFactory.getIconSuperDrawable(context,
icon,
24,
true,
tintColor),
this,
imageId,
tintColor)
} catch (e: Exception) {
Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
} }
} }

View File

@@ -95,7 +95,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener { fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) { override fun onItemClick(item: Field) {
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1) currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
goNextAutomatically() actionTabAutomatically()
} }
} }
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true) recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
@@ -225,19 +225,19 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
if (entryInfoKey != null) { if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.username, 1) currentInputConnection.commitText(entryInfoKey!!.username, 1)
} }
goNextAutomatically() actionTabAutomatically()
} }
KEY_PASSWORD -> { KEY_PASSWORD -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.password, 1) currentInputConnection.commitText(entryInfoKey!!.password, 1)
} }
goNextAutomatically() actionGoAutomatically()
} }
KEY_URL -> { KEY_URL -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.url, 1) currentInputConnection.commitText(entryInfoKey!!.url, 1)
} }
goNextAutomatically() actionTabAutomatically()
} }
KEY_FIELDS -> { KEY_FIELDS -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
@@ -250,10 +250,15 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
} }
Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0) Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0)
Keyboard.KEYCODE_DONE -> inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO) Keyboard.KEYCODE_DONE -> inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
}// TODO Unlock key }
} }
private fun goNextAutomatically() { private fun actionTabAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
}
private fun actionGoAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this)) if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO) currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
} }

View File

@@ -21,6 +21,7 @@ package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import java.util.* import java.util.*
@@ -29,6 +30,7 @@ class EntryInfo : Parcelable {
var id: String = "" var id: String = ""
var title: String = "" var title: String = ""
var icon: IconImage? = null
var username: String = "" var username: String = ""
var password: String = "" var password: String = ""
var url: String = "" var url: String = ""
@@ -41,6 +43,7 @@ class EntryInfo : Parcelable {
private constructor(parcel: Parcel) { private constructor(parcel: Parcel) {
id = parcel.readString() ?: id id = parcel.readString() ?: id
title = parcel.readString() ?: title title = parcel.readString() ?: title
icon = parcel.readParcelable(IconImage::class.java.classLoader)
username = parcel.readString() ?: username username = parcel.readString() ?: username
password = parcel.readString() ?: password password = parcel.readString() ?: password
url = parcel.readString() ?: url url = parcel.readString() ?: url
@@ -56,6 +59,7 @@ class EntryInfo : Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) { override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(id) parcel.writeString(id)
parcel.writeString(title) parcel.writeString(title)
parcel.writeParcelable(icon, flags)
parcel.writeString(username) parcel.writeString(username)
parcel.writeString(password) parcel.writeString(password)
parcel.writeString(url) parcel.writeString(url)

View File

@@ -0,0 +1,42 @@
package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.Parcelable
class SearchInfo : Parcelable {
var applicationId: String? = null
var webDomain: String? = null
constructor()
private constructor(parcel: Parcel) {
val readAppId = parcel.readString()
applicationId = if (readAppId.isNullOrEmpty()) null else readAppId
val readDomain = parcel.readString()
webDomain = if (readDomain.isNullOrEmpty()) null else readDomain
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(applicationId ?: "")
parcel.writeString(webDomain ?: "")
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<SearchInfo> = object : Parcelable.Creator<SearchInfo> {
override fun createFromParcel(parcel: Parcel): SearchInfo {
return SearchInfo(parcel)
}
override fun newArray(size: Int): Array<SearchInfo?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -21,10 +21,7 @@ package com.kunzisoft.keepass.notifications
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.AsyncTask import android.os.*
import android.os.Binder
import android.os.Bundle
import android.os.IBinder
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.* import com.kunzisoft.keepass.database.action.*
@@ -42,6 +39,7 @@ import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION 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 java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdater { class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdater {
@@ -63,6 +61,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
fun addActionTaskListener(actionTaskListener: ActionTaskListener) { fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.add(actionTaskListener) mActionTaskListeners.add(actionTaskListener)
// To prevent task dialog to be unbound before the display
actionRunnableAsyncTask?.allowFinishTask?.set(true)
} }
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) { fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
@@ -571,6 +571,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
private val onPostExecute: (result: ActionRunnable.Result) -> Unit) private val onPostExecute: (result: ActionRunnable.Result) -> Unit)
: AsyncTask<((ProgressTaskUpdater?) -> ActionRunnable), Void, ActionRunnable.Result>() { : AsyncTask<((ProgressTaskUpdater?) -> ActionRunnable), Void, ActionRunnable.Result>() {
var allowFinishTask = AtomicBoolean(false)
override fun onPreExecute() { override fun onPreExecute() {
super.onPreExecute() super.onPreExecute()
onPreExecute.invoke() onPreExecute.invoke()
@@ -578,14 +580,16 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
override fun doInBackground(vararg actionRunnables: ((ProgressTaskUpdater?)-> ActionRunnable)?): ActionRunnable.Result { override fun doInBackground(vararg actionRunnables: ((ProgressTaskUpdater?)-> ActionRunnable)?): ActionRunnable.Result {
var resultTask = ActionRunnable.Result(false) var resultTask = ActionRunnable.Result(false)
// Without that, bind listeners don't work properly (I don't know why?)
Thread.sleep(500)
actionRunnables.forEach { actionRunnables.forEach {
it?.invoke(progressTaskUpdater)?.apply { it?.invoke(progressTaskUpdater)?.apply {
run() run()
resultTask = result resultTask = result
} }
} }
// Additional wait if the dialog take time to show
while(!allowFinishTask.get()) {
Thread.sleep(250)
}
return resultTask return resultTask
} }

View File

@@ -53,15 +53,12 @@ abstract class NotificationService : Service() {
protected fun buildNewNotification(): NotificationCompat.Builder { protected fun buildNewNotification(): NotificationCompat.Builder {
return NotificationCompat.Builder(this, CHANNEL_ID_KEEPASS) return NotificationCompat.Builder(this, CHANNEL_ID_KEEPASS)
.setColor(colorNotificationAccent) .setColor(colorNotificationAccent)
//TODO .setGroup(GROUP_KEEPASS)
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_SECRET) .setVisibility(NotificationCompat.VISIBILITY_SECRET)
} }
// TODO only for > lollipop
protected fun buildSummaryNotification(): NotificationCompat.Builder { protected fun buildSummaryNotification(): NotificationCompat.Builder {
return buildNewNotification().apply { return buildNewNotification().apply {
// TODO Ic setSmallIcon(R.drawable.notification_ic_data_usage_24dp)
setGroupSummary(true) setGroupSummary(true)
} }
} }
@@ -75,7 +72,5 @@ abstract class NotificationService : Service() {
companion object { companion object {
const val CHANNEL_ID_KEEPASS = "com.kunzisoft.keepass.notification.channel" const val CHANNEL_ID_KEEPASS = "com.kunzisoft.keepass.notification.channel"
const val CHANNEL_NAME_KEEPASS = "KeePassDX notification" const val CHANNEL_NAME_KEEPASS = "KeePassDX notification"
const val GROUP_KEEPASS = "GROUP_KEEPASS"
const val SUMMARY_ID = 0
} }
} }

View File

@@ -74,7 +74,7 @@ object OtpEntryFields {
// [^&=\s]+=[^&=\s]+(&[^&=\s]+=[^&=\s]+)* // [^&=\s]+=[^&=\s]+(&[^&=\s]+=[^&=\s]+)*
private const val validKeyValue = "[^&=\\s]+" private const val validKeyValue = "[^&=\\s]+"
private const val validKeyValuePair = "$validKeyValue=$validKeyValue" private const val validKeyValuePair = "$validKeyValue=$validKeyValue"
private const val validKeyValueRegex = "$validKeyValuePair&($validKeyValuePair)*" private const val validKeyValueRegex = "$validKeyValuePair(&$validKeyValuePair)*"
/** /**
* Parse fields of an entry to retrieve an OtpElement * Parse fields of an entry to retrieve an OtpElement
@@ -243,21 +243,18 @@ object OtpEntryFields {
val plainText = getField(OTP_FIELD) val plainText = getField(OTP_FIELD)
if (plainText != null && plainText.isNotEmpty()) { if (plainText != null && plainText.isNotEmpty()) {
if (Pattern.matches(validKeyValueRegex, plainText)) { if (Pattern.matches(validKeyValueRegex, plainText)) {
try { return try {
// KeeOtp string format // KeeOtp string format
val query = breakDownKeyValuePairs(plainText) val query = breakDownKeyValuePairs(plainText)
var secretString = query[SEED_KEY] otpElement.setBase32Secret(query[SEED_KEY] ?: "")
if (secretString == null) otpElement.digits = query[DIGITS_KEY]?.toIntOrNull() ?: OTP_DEFAULT_DIGITS
secretString = "" otpElement.period = query[STEP_KEY]?.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
otpElement.setBase32Secret(secretString)
otpElement.digits = query[DIGITS_KEY]?.toIntOrNull() ?: OTP_DEFAULT_DIGITS
otpElement.period = query[STEP_KEY]?.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
otpElement.type = OtpType.TOTP otpElement.type = OtpType.TOTP
return true true
} catch (exception: Exception) { } catch (exception: Exception) {
return false false
} }
} else { } else {
// Malformed // Malformed

View File

@@ -65,14 +65,14 @@ class PasswordGenerator(private val resources: Resources) {
// No option has been checked // No option has been checked
if (!upperCase if (!upperCase
&& !lowerCase && !lowerCase
&& !digits && !digits
&& !minus && !minus
&& !underline && !underline
&& !space && !space
&& !specials && !specials
&& !brackets && !brackets
&& !extended) { && !extended) {
throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type)) throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type))
} }
@@ -114,35 +114,27 @@ class PasswordGenerator(private val resources: Resources) {
if (upperCase) { if (upperCase) {
charSet.append(UPPERCASE_CHARS) charSet.append(UPPERCASE_CHARS)
} }
if (lowerCase) { if (lowerCase) {
charSet.append(LOWERCASE_CHARS) charSet.append(LOWERCASE_CHARS)
} }
if (digits) { if (digits) {
charSet.append(DIGIT_CHARS) charSet.append(DIGIT_CHARS)
} }
if (minus) { if (minus) {
charSet.append(MINUS_CHAR) charSet.append(MINUS_CHAR)
} }
if (underline) { if (underline) {
charSet.append(UNDERLINE_CHAR) charSet.append(UNDERLINE_CHAR)
} }
if (space) { if (space) {
charSet.append(SPACE_CHAR) charSet.append(SPACE_CHAR)
} }
if (specials) { if (specials) {
charSet.append(SPECIAL_CHARS) charSet.append(SPECIAL_CHARS)
} }
if (brackets) { if (brackets) {
charSet.append(BRACKET_CHARS) charSet.append(BRACKET_CHARS)
} }
if (extended) { if (extended) {
charSet.append(extendedChars()) charSet.append(extendedChars())
} }

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
class AutofillSettingsActivity : StylishActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_toolbar)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.setTitle(R.string.autofill_preference_title)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, AutofillSettingsFragment())
.commit()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> onBackPressed()
}
return super.onOptionsItemSelected(item)
}
}

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.
* *
@@ -20,16 +20,14 @@
package com.kunzisoft.keepass.settings package com.kunzisoft.keepass.settings
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.preference.PreferenceFragmentCompat
class SettingsAutofillActivity : SettingsActivity() { import com.kunzisoft.keepass.R
override fun onCreate(savedInstanceState: Bundle?) { class AutofillSettingsFragment : PreferenceFragmentCompat() {
super.onCreate(savedInstanceState)
mTimeoutEnable = false
}
override fun retrieveMainFragment(): Fragment { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
return NestedSettingsFragment.newInstance(NestedSettingsFragment.Screen.FORM_FILLING) // Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences_autofill, rootKey)
} }
} }

View File

@@ -26,7 +26,7 @@ import android.view.MenuItem
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
class MagikIMESettings : StylishActivity() { class MagikeyboardSettingsActivity : StylishActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -39,7 +39,7 @@ class MagikIMESettings : StylishActivity() {
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, MagikIMESettingsFragment()) .replace(R.id.fragment_container, MagikeyboardSettingsFragment())
.commit() .commit()
} }
} }

View File

@@ -24,7 +24,7 @@ import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
class MagikIMESettingsFragment : PreferenceFragmentCompat() { class MagikeyboardSettingsFragment : 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

View File

@@ -24,7 +24,6 @@ import android.os.Bundle
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
class MainPreferenceFragment : PreferenceFragmentCompat() { class MainPreferenceFragment : PreferenceFragmentCompat() {

View File

@@ -105,7 +105,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
override fun onPreferenceClick(preference: Preference): Boolean { override fun onPreferenceClick(preference: Preference): Boolean {
if ((preference as SwitchPreference).isChecked) { if ((preference as SwitchPreference).isChecked) {
try { try {
startEnableService() enableService()
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
val error = getString(R.string.error_autofill_enable_service) val error = getString(R.string.error_autofill_enable_service)
preference.isChecked = false preference.isChecked = false
@@ -124,21 +124,20 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) { if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) {
autofillManager.disableAutofillServices() autofillManager.disableAutofillServices()
} else { } else {
Log.d(javaClass.name, "Sample service already disabled.") Log.d(javaClass.name, "Autofill service already disabled.")
} }
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
@Throws(ActivityNotFoundException::class) @Throws(ActivityNotFoundException::class)
private fun startEnableService() { private fun enableService() {
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) { if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE) val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
// TODO Autofill intent.data = Uri.parse("package:com.kunzisoft.keepass.autofill.KeeAutofillService")
intent.data = Uri.parse("package:com.example.android.autofill.service") Log.d(javaClass.name, "Autofill enable service: intent=$intent")
Log.d(javaClass.name, "enableService(): intent=$intent")
startActivityForResult(intent, REQUEST_CODE_AUTOFILL) startActivityForResult(intent, REQUEST_CODE_AUTOFILL)
} else { } else {
Log.d(javaClass.name, "Sample service already enabled.") Log.d(javaClass.name, "Autofill service already enabled.")
} }
} }
} }
@@ -148,7 +147,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url) UriUtil.gotoUrl(requireContext(), R.string.magic_keyboard_explanation_url)
false false
} }
@@ -160,17 +159,22 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
findPreference<Preference>(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener {
startActivity(Intent(context, MagikIMESettings::class.java)) startActivity(Intent(context, MagikeyboardSettingsActivity::class.java))
false
}
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url)
false false
} }
findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(context!!, R.string.autofill_explanation_url) UriUtil.gotoUrl(requireContext(), R.string.autofill_explanation_url)
false
}
findPreference<Preference>(getString(R.string.settings_autofill_key))?.setOnPreferenceClickListener {
startActivity(Intent(context, AutofillSettingsActivity::class.java))
false
}
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(requireContext(), R.string.clipboard_explanation_url)
false false
} }
@@ -186,7 +190,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
"\n\n" + "\n\n" +
getString(R.string.clipboard_warning) getString(R.string.clipboard_warning)
AlertDialog AlertDialog
.Builder(context!!) .Builder(requireContext())
.setMessage(message) .setMessage(message)
.create() .create()
.apply { .apply {
@@ -223,11 +227,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
biometricUnlockEnablePreference?.apply { biometricUnlockEnablePreference?.apply {
isChecked = false isChecked = false
setOnPreferenceClickListener { preference -> setOnPreferenceClickListener { preference ->
fragmentManager?.let { fragmentManager -> (preference as SwitchPreference).isChecked = false
(preference as SwitchPreference).isChecked = false UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M) .show(parentFragmentManager, "unavailableFeatureDialog")
.show(fragmentManager, "unavailableFeatureDialog")
}
false false
} }
} }
@@ -274,7 +276,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(context!!, R.string.advanced_unlock_explanation_url) UriUtil.gotoUrl(requireContext(), R.string.advanced_unlock_explanation_url)
false false
} }
} }
@@ -289,13 +291,11 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue -> findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
var styleEnabled = true var styleEnabled = true
val styleIdString = newValue as String val styleIdString = newValue as String
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!)) if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(activity))
for (themeIdDisabled in BuildConfig.STYLES_DISABLED) { for (themeIdDisabled in BuildConfig.STYLES_DISABLED) {
if (themeIdDisabled == styleIdString) { if (themeIdDisabled == styleIdString) {
styleEnabled = false styleEnabled = false
fragmentManager?.let { fragmentManager -> ProFeatureDialogFragment().show(parentFragmentManager, "pro_feature_dialog")
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
}
} }
} }
if (styleEnabled) { if (styleEnabled) {
@@ -308,13 +308,11 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue -> findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue ->
var iconPackEnabled = true var iconPackEnabled = true
val iconPackId = newValue as String val iconPackId = newValue as String
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!)) if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(activity))
for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) { for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) {
if (iconPackIdDisabled == iconPackId) { if (iconPackIdDisabled == iconPackId) {
iconPackEnabled = false iconPackEnabled = false
fragmentManager?.let { fragmentManager -> ProFeatureDialogFragment().show(parentFragmentManager, "pro_feature_dialog")
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
}
} }
} }
if (iconPackEnabled) { if (iconPackEnabled) {
@@ -326,7 +324,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<Preference>(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener { findPreference<Preference>(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener {
// To allow only one toast // To allow only one toast
if (mCount == 0) { if (mCount == 0) {
val sharedPreferences = Education.getEducationSharedPreferences(context!!) val sharedPreferences = Education.getEducationSharedPreferences(activity)
val editor = sharedPreferences.edit() val editor = sharedPreferences.edit()
for (resourceId in Education.educationResourcesKeys) { for (resourceId in Education.educationResourcesKeys) {
editor.putBoolean(getString(resourceId), false) editor.putBoolean(getString(resourceId), false)

View File

@@ -31,7 +31,6 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.lock
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
@@ -252,10 +251,8 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply { findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
isEnabled = if (!mDatabaseReadOnly) { isEnabled = if (!mDatabaseReadOnly) {
onPreferenceClickListener = Preference.OnPreferenceClickListener { onPreferenceClickListener = Preference.OnPreferenceClickListener {
fragmentManager?.let { fragmentManager -> AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey)
AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey) .show(parentFragmentManager, "passwordDialog")
.show(fragmentManager, "passwordDialog")
}
false false
} }
true true
@@ -282,7 +279,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
try { try {
// To reassign color listener after orientation change // To reassign color listener after orientation change
val chromaDialog = fragmentManager?.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat? val chromaDialog = parentFragmentManager.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
chromaDialog?.onColorSelectedListener = colorSelectedListener chromaDialog?.onColorSelectedListener = colorSelectedListener
} catch (e: Exception) {} } catch (e: Exception) {}
@@ -445,8 +442,8 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
mMemoryPref?.summary = memoryToShow.toString() mMemoryPref?.summary = memoryToShow.toString()
} }
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_PARALLELISM_TASK -> { DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_PARALLELISM_TASK -> {
val oldParallelism = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) val oldParallelism = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
val newParallelism = data.getInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) val newParallelism = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
val parallelismToShow = val parallelismToShow =
if (result.isSuccess) { if (result.isSuccess) {
newParallelism newParallelism
@@ -465,68 +462,64 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
var otherDialogFragment = false var otherDialogFragment = false
fragmentManager?.let { fragmentManager -> var dialogFragment: DialogFragment? = null
preference?.let { preference -> // Main Preferences
var dialogFragment: DialogFragment? = null when (preference?.key) {
when { getString(R.string.database_name_key) -> {
// Main Preferences dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key)
preference.key == getString(R.string.database_name_key) -> { }
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key) getString(R.string.database_description_key) -> {
} dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key)
preference.key == getString(R.string.database_description_key) -> { }
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key) getString(R.string.database_default_username_key) -> {
} dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key)
preference.key == getString(R.string.database_default_username_key) -> { }
dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key) getString(R.string.database_custom_color_key) -> {
} dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply {
preference.key == getString(R.string.database_custom_color_key) -> { onColorSelectedListener = colorSelectedListener
dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply {
onColorSelectedListener = colorSelectedListener
}
}
preference.key == getString(R.string.database_data_compression_key) -> {
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.max_history_items_key) -> {
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.max_history_size_key) -> {
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
}
// Security
preference.key == getString(R.string.encryption_algorithm_key) -> {
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.key_derivation_function_key) -> {
val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key)
// Add other prefs to manage
keyDerivationDialogFragment.setRoundPreference(mRoundPref)
keyDerivationDialogFragment.setMemoryPreference(mMemoryPref)
keyDerivationDialogFragment.setParallelismPreference(mParallelismPref)
dialogFragment = keyDerivationDialogFragment
}
preference.key == getString(R.string.transform_rounds_key) -> {
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.memory_usage_key) -> {
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.parallelism_key) -> {
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
}
else -> otherDialogFragment = true
}
if (dialogFragment != null && !mDatabaseReadOnly) {
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(fragmentManager, TAG_PREF_FRAGMENT)
}
// Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference)
} }
} }
getString(R.string.database_data_compression_key) -> {
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.max_history_items_key) -> {
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.max_history_size_key) -> {
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
}
// Security
getString(R.string.encryption_algorithm_key) -> {
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.key_derivation_function_key) -> {
val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key)
// Add other prefs to manage
keyDerivationDialogFragment.setRoundPreference(mRoundPref)
keyDerivationDialogFragment.setMemoryPreference(mMemoryPref)
keyDerivationDialogFragment.setParallelismPreference(mParallelismPref)
dialogFragment = keyDerivationDialogFragment
}
getString(R.string.transform_rounds_key) -> {
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.memory_usage_key) -> {
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.parallelism_key) -> {
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
}
else -> otherDialogFragment = true
}
if (dialogFragment != null && !mDatabaseReadOnly) {
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
// Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference)
} }
} }
@@ -551,14 +544,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
val settingActivity = activity as SettingsActivity? val settingActivity = activity as SettingsActivity?
when (item.itemId) { return when (item.itemId) {
R.id.menu_lock -> {
settingActivity?.lock()
return true
}
R.id.menu_save_database -> { R.id.menu_save_database -> {
settingActivity?.mProgressDialogThread?.startDatabaseSave(!mDatabaseReadOnly) settingActivity?.mProgressDialogThread?.startDatabaseSave(!mDatabaseReadOnly)
return true true
} }
else -> { else -> {
@@ -566,7 +555,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
settingActivity?.let { settingActivity?.let {
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true) MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true)
} }
return super.onOptionsItemSelected(item) super.onOptionsItemSelected(item)
} }
} }
} }

View File

@@ -36,12 +36,10 @@ abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
} }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
onCreateScreenPreference(
var key = 0 Screen.values()[requireArguments().getInt(TAG_KEY)],
if (arguments != null) savedInstanceState,
key = arguments!!.getInt(TAG_KEY) rootKey)
onCreateScreenPreference(Screen.values()[key], savedInstanceState, rootKey)
} }
abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?)
@@ -51,13 +49,11 @@ abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
protected fun preferenceInDevelopment(preferenceInDev: Preference) { protected fun preferenceInDevelopment(preferenceInDev: Preference) {
preferenceInDev.setOnPreferenceClickListener { preference -> preferenceInDev.setOnPreferenceClickListener { preference ->
fragmentManager?.let { fragmentManager -> try { // don't check if we can
try { // don't check if we can (preference as SwitchPreference).isChecked = false
(preference as SwitchPreference).isChecked = false } catch (ignored: Exception) {
} catch (ignored: Exception) {
}
UnderDevelopmentFeatureDialogFragment().show(fragmentManager, "underDevFeatureDialog")
} }
UnderDevelopmentFeatureDialogFragment().show(parentFragmentManager, "underDevFeatureDialog")
false false
} }
} }

View File

@@ -199,6 +199,12 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.lock_database_back_root_default)) context.resources.getBoolean(R.bool.lock_database_back_root_default))
} }
fun showLockDatabaseButton(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.lock_database_show_button_key),
context.resources.getBoolean(R.bool.lock_database_show_button_default))
}
fun isAutoSaveDatabaseEnabled(context: Context): Boolean { fun isAutoSaveDatabaseEnabled(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.enable_auto_save_database_key), return prefs.getBoolean(context.getString(R.string.enable_auto_save_database_key),
@@ -348,4 +354,10 @@ object PreferencesUtil {
return prefs.getBoolean(context.getString(R.string.keyboard_key_sound_key), return prefs.getBoolean(context.getString(R.string.keyboard_key_sound_key),
context.resources.getBoolean(R.bool.keyboard_key_sound_default)) context.resources.getBoolean(R.bool.keyboard_key_sound_default))
} }
fun isAutofillAutoSearchEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
context.resources.getBoolean(R.bool.autofill_auto_search_default))
}
} }

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