Compare commits

...

618 Commits

Author SHA1 Message Date
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
J-Jamet
32d235e8c7 Merge branch 'release/2.5beta30' 2020-03-27 18:13:21 +01:00
J-Jamet
cb982b3513 Fix strong tag 2020-03-27 17:53:42 +01:00
J-Jamet
d7ed6c26dd Merge branch 'develop' into translations 2020-03-27 17:49:29 +01:00
J-Jamet
8ff19f7e68 First string pass according to #460 2020-03-27 17:43:31 +01:00
J-Jamet
729e062c3a Add error when create database file 2020-03-27 17:12:54 +01:00
J-Jamet
7d0340ac07 Upgrade CHANGELOG 2020-03-27 16:57:35 +01:00
J-Jamet
01960e74c1 Fix check file ANR #494 2020-03-27 16:36:57 +01:00
anonymous
8e40250985 Translated using Weblate (German)
Currently translated at 96.4% (411 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-27 00:46:37 +01:00
C. Rüdinger
8ae2edb61a Translated using Weblate (German)
Currently translated at 96.4% (411 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-27 00:46:37 +01:00
J-Jamet
0baa7bcbf1 Change discard button 2020-03-26 16:36:17 +01:00
J-Jamet
fffee48918 Upgrade version to 2.5beta30 2020-03-26 14:50:20 +01:00
J-Jamet
515abb6e14 Better UUID view 2020-03-26 14:45:51 +01:00
J-Jamet
6c1c3ff87f New method to wait 1.5 seconds after screen turns off 2020-03-26 13:38:20 +01:00
J-Jamet
5b65575c7a Add comment to ignore autocomplete off 2020-03-26 12:57:24 +01:00
J-Jamet
0f258fc5f8 Upgrade autofill algorithm 2020-03-26 12:47:32 +01:00
anonymous
206bc661dc Translated using Weblate (German)
Currently translated at 96.2% (410 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-25 23:44:32 +01:00
J-Jamet
d0e35b109e Revert : Fix Nextcloud File Upload conflict #497 2020-03-25 22:48:15 +01:00
J-Jamet
61769c4f20 Fix Nextcloud File Upload conflict #497 2020-03-25 21:07:36 +01:00
Hosted Weblate
95778ee5f4 Merge branch 'origin/master' into Weblate. 2020-03-25 12:21:35 +01:00
anonymous
155030fdca Translated using Weblate (Italian)
Currently translated at 79.5% (339 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-03-25 12:21:35 +01:00
Filippo De Bortoli
98237ef76c Translated using Weblate (Italian)
Currently translated at 79.5% (339 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-03-25 12:21:34 +01:00
J-Jamet
f0a7b38199 Merge tag '2.5beta29' into develop
2.5beta29
2020-03-25 12:06:20 +01:00
J-Jamet
9fc5e6751b Merge branch 'release/2.5beta29' 2020-03-25 12:06:08 +01:00
J-Jamet
4c1630312b Add UUID color 2020-03-25 11:46:43 +01:00
J-Jamet
d397c5c996 Add UUID ref for entry 2020-03-25 11:22:19 +01:00
J-Jamet
f6c41b5a60 Replace the strong nodes 2020-03-24 20:49:33 +01:00
J-Jamet
06eb5c01c3 Update versions 2020-03-24 20:44:50 +01:00
J-Jamet
7df49f91e8 Merge branch 'release/2.5beta29' of github.com:Kunzisoft/KeePassDX into release/2.5beta29 2020-03-24 20:10:27 +01:00
J-Jamet
96dcbb0ce7 Merge branch 'develop' into release/2.5beta29 2020-03-24 20:09:47 +01:00
J-Jamet
5f828fb986 Fix setting 2020-03-24 20:09:30 +01:00
J-Jamet
533d663938 Merge branch 'translations' into develop 2020-03-24 19:44:38 +01:00
J-Jamet
ae788503a9 Fix html_about licence 2020-03-24 19:44:15 +01:00
anonymous
cf0acd9c73 Translated using Weblate (Italian)
Currently translated at 78.4% (334 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-03-24 11:42:45 +01:00
Filippo De Bortoli
0857f2f1cf Translated using Weblate (Italian)
Currently translated at 78.4% (334 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-03-24 11:42:43 +01:00
Ema Panz
c05d412bdb Translated using Weblate (Italian)
Currently translated at 71.1% (303 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-03-24 11:18:14 +01:00
anonymous
c8e0ce717d Translated using Weblate (Italian)
Currently translated at 71.1% (303 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-03-24 11:18:14 +01:00
J-Jamet
cc3485b201 Update Readme 2020-03-23 21:10:28 +01:00
J-Jamet
81ea7080c2 Show UUID as KeePass UUID 2020-03-21 20:25:37 +01:00
J-Jamet
76ff6f5ae0 Fix reference loop 2020-03-21 19:01:05 +01:00
J-Jamet
0c0d0b7a6f Encapsulate readonly for launch method 2020-03-21 17:23:17 +01:00
J-Jamet
ec8cf1f6b7 Move autofill setting up 2020-03-21 17:16:49 +01:00
J-Jamet
da44310d1b Fix focus validation button 2020-03-21 17:03:36 +01:00
J-Jamet
4bd9c84bb0 Add dialog to validate discard entry changes 2020-03-21 16:52:50 +01:00
J-Jamet
3b1269a770 Fix bind listeners 2020-03-21 16:12:07 +01:00
J-Jamet
7c986ccee8 Fix card_view_margin issue 2020-03-21 16:08:23 +01:00
J-Jamet
903bad8f36 Fix launch of open db service exception 2020-03-21 14:36:33 +01:00
J-Jamet
4b9577437c Hide add button when nodes are selected 2020-03-21 12:27:18 +01:00
J-Jamet
c316011fbc Show toast on invalid key exception 2020-03-21 12:16:45 +01:00
J-Jamet
3fb1f18c22 Education screen for OTP and entry edit menu 2020-03-19 19:47:51 +01:00
J-Jamet
53935058f5 Add OTP icon 2020-03-19 19:16:38 +01:00
J-Jamet
a3860c9581 Generate password icon as dice 2020-03-19 18:34:14 +01:00
J-Jamet
dc20899d26 Update CHANGELOG 2020-03-19 17:48:56 +01:00
J-Jamet
62ac3ddb75 Merge branch 'feature/Autofill_Improvement' into develop 2020-03-19 17:23:24 +01:00
J-Jamet
b792a61bf9 Improve hint "on" and "off" recognition 2020-03-19 15:18:06 +01:00
J-Jamet
aae9f9e1cb Warning when cancel autofill 2020-03-19 14:59:33 +01:00
J-Jamet
d098bf5e6a Upgrade Autofill algorithm 2020-03-19 14:35:22 +01:00
J-Jamet
b0e8a3ecd9 Add expiration datetime 2020-03-18 12:03:12 +01:00
J-Jamet
4efa684022 Merge branch 'feature/Edit_Expired_Date' into develop 2020-03-18 11:53:46 +01:00
J-Jamet
f2c8082990 Fix date picker kitkat issue 2020-03-18 11:53:21 +01:00
J-Jamet
1abba80045 Fix dialog theme 2020-03-18 11:45:52 +01:00
J-Jamet
68564a2b75 Merge branch 'develop' into feature/Edit_Expired_Date 2020-03-18 09:46:28 +01:00
J-Jamet
385b701b38 Fix cardview margin 2020-03-18 09:42:36 +01:00
Éfrit
11c9a1d707 Translated using Weblate (French)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-03-17 00:42:55 +01:00
J-Jamet
c5aef6b561 New entry edit tool menu style 2020-03-15 12:21:26 +01:00
J-Jamet
dfcf73cfd0 Merge branch 'develop' into feature/Edit_Expired_Date 2020-03-14 21:15:58 +01:00
J-Jamet
7fc9389700 Always show menu 2020-03-14 21:15:37 +01:00
J-Jamet
d1af7349bc Edit button as validation 2020-03-14 20:51:47 +01:00
J-Jamet
0fd955197d Expires selection 2020-03-14 20:16:36 +01:00
Destiny Li
cbf33507d1 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-03-14 17:37:29 +01:00
WaldiS
bc60a5d97e Translated using Weblate (Polish)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-03-14 17:37:27 +01:00
zeritti
57596b2991 Translated using Weblate (Czech)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-03-14 17:37:23 +01:00
solokot
43b3602a52 Translated using Weblate (Russian)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-03-14 17:37:22 +01:00
J-Jamet
c09ec961b8 Merge branch 'feature/Entry_Edit_BottomBar' into develop 2020-03-14 11:00:47 +01:00
J-Jamet
d140b453b2 Merge branch 'feature/Entry_Edit_BottomBar' into develop 2020-03-14 10:36:52 +01:00
J-Jamet
9eb42636ec Change read icons 2020-03-13 21:58:32 +01:00
J-Jamet
ee2d663fce New icons for entry edit tools 2020-03-13 21:33:38 +01:00
J-Jamet
8e83615a22 Add Edit Toolbar menu on top 2020-03-13 17:06:09 +01:00
J-Jamet
0ff129c5ca Edit Entry with Card View 2020-03-13 11:06:56 +01:00
WaldiS
e49858439f Translated using Weblate (Polish)
Currently translated at 99.5% (424 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-03-12 20:33:30 +01:00
zeritti
3df07f7f47 Translated using Weblate (Czech)
Currently translated at 99.5% (424 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-03-12 20:33:30 +01:00
Aurel F
ff9e179593 Translated using Weblate (Romanian)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2020-03-12 20:33:28 +01:00
J-Jamet
7539fee04b Add BottomBar in EntryEdit 2020-03-12 19:15:03 +01:00
J-Jamet
71a339a58f Suppress small warning 2020-03-12 17:15:17 +01:00
J-Jamet
9ef2ea016b Suppress deprecation for Keyboard 2020-03-12 17:14:46 +01:00
J-Jamet
de4936a16d Set nullable getSystemService 2020-03-12 17:05:53 +01:00
J-Jamet
574d2b8904 Wait 3 seconds before the lock after the screen turns off #59 2020-03-12 16:56:46 +01:00
J-Jamet
1f3f7634e7 Allow empty title in entries #423 2020-03-12 14:41:02 +01:00
J-Jamet
3c0725baff Fix remember key file option 2020-03-12 14:24:33 +01:00
J-Jamet
b0e1411012 Update CHANGELOG 2020-03-12 14:20:48 +01:00
J-Jamet
39daf4714d Merge branch 'feature/Delete_Registered_Keyfile_New_Credentials' into develop 2020-03-12 14:17:35 +01:00
J-Jamet
4706afa823 Fix credentials options 2020-03-12 14:16:30 +01:00
J-Jamet
25977d389d Add DAO command to delete registered keyfile 2020-03-12 13:18:36 +01:00
J-Jamet
f760110569 default_database_path_key in strings.xml 2020-03-12 09:28:46 +01:00
J-Jamet
133e78fe97 Update CHANGELOG 2020-03-12 09:20:29 +01:00
J-Jamet
d92e0c8620 Fix magikeyboard lock 2020-03-11 19:17:51 +01:00
J-Jamet
62fdb69d6b Fix small element 2020-03-11 18:56:45 +01:00
J-Jamet
def57c9fb2 Merge branch 'feature/Lock_Database' issue 483 2020-03-11 18:42:34 +01:00
J-Jamet
21c9c898c3 Add lock timer in service, notification remains lock to capture the broadcast 2020-03-11 18:30:11 +01:00
J-Jamet
1f03c922c2 Encapsulate lock broadcast 2020-03-11 16:46:44 +01:00
J-Jamet
3f6ae6bdac Fix node update #487 2020-03-11 12:42:18 +01:00
jan madsen
60615ee1eb Translated using Weblate (Danish)
Currently translated at 98.5% (420 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-03-10 20:23:08 +01:00
J-Jamet
92b0d1bfa9 Upgrade to version 2.5beta29 2020-03-10 19:02:37 +01:00
J-Jamet
237988dc1f Merge tag '2.5beta28' into develop
2.5beta28
2020-03-10 19:00:08 +01:00
J-Jamet
a846ec29ca Merge branch 'release/2.5beta28' 2020-03-10 18:59:59 +01:00
J-Jamet
4533e96bff Upgrade CHANGELOG 2020-03-10 18:48:57 +01:00
J-Jamet
0a401c3ac9 Fix strings 2020-03-10 18:41:35 +01:00
J-Jamet
468abaf077 Fix strings 2020-03-10 18:29:19 +01:00
J-Jamet
4ccf2f641c Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into develop 2020-03-10 18:14:14 +01:00
Dominik Baláž
34eb2785cf Translated using Weblate (Slovak)
Currently translated at 21.5% (92 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sk/
2020-03-10 17:21:26 +01:00
anonymous
09dbfe323e Translated using Weblate (Slovak)
Currently translated at 21.5% (92 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sk/
2020-03-10 17:21:26 +01:00
Destiny Li
1f06c5b425 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-03-10 11:33:21 +01:00
Destiny Li
b98e089f7a Translated using Weblate (Chinese (Traditional))
Currently translated at 48.3% (206 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2020-03-10 11:33:20 +01:00
Aurel F
a0ad06ed0a Translated using Weblate (Romanian)
Currently translated at 40.8% (174 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2020-03-10 11:33:18 +01:00
solokot
ec63365429 Translated using Weblate (Russian)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-03-10 11:33:10 +01:00
J-Jamet
2cb85e4346 Update items after appearance change #452 2020-03-10 08:58:22 +01:00
J-Jamet
0d7c479c51 Merge branch 'feature/Migration_SDK_29' into develop 2020-03-10 08:40:53 +01:00
J-Jamet
5a6c21e662 Try to fix read only bug #480 2020-03-10 08:38:49 +01:00
J-Jamet
d6cadac98f Fix small warning 2020-03-08 13:22:11 +01:00
J-Jamet
dac2fc2c37 Use PreferenceManager from AndroidX 2020-03-08 13:10:16 +01:00
J-Jamet
0fb45cef0d Encapsulate preferences 2020-03-08 13:04:25 +01:00
anonymous
5ebdbd4003 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-03-08 12:08:21 +01:00
Destiny Li
b30f1023cb Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-03-08 12:08:21 +01:00
J-Jamet
e5f65a4d1e Fix % issue in string 2020-03-08 11:48:47 +01:00
J-Jamet
ab42a65aa4 Fix null issues 2020-03-08 11:41:13 +01:00
J-Jamet
e351456bfe First pass migration SDK 29 2020-03-08 11:03:21 +01:00
J-Jamet
452e68b08f Fix C cast warning 2020-03-08 10:52:29 +01:00
J-Jamet
d65beed7a1 Fix small warnings 2020-03-08 10:49:06 +01:00
J-Jamet
f5a5a0e8cb Add generated JSON for database 2020-03-08 10:41:52 +01:00
solokot
98380a0906 Translated using Weblate (Russian)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-03-08 10:20:37 +01:00
J-Jamet
22fe7508f3 Fix doForEachChild warning 2020-03-08 10:16:31 +01:00
J-Jamet
c8e241fc76 Fix Room incremental warning 2020-03-08 09:50:32 +01:00
J-Jamet
2c943e00d0 Fix writeEnum warning 2020-03-08 09:50:07 +01:00
J-Jamet
7ddb83b72d Fix DateFormatter warning 2020-03-08 09:36:45 +01:00
Kunzisoft
50bac01699 Translated using Weblate (French)
Currently translated at 99.7% (425 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-03-08 09:33:23 +01:00
Aurel F
e0a92dfadd Translated using Weblate (Romanian)
Currently translated at 12.2% (52 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2020-03-08 09:33:22 +01:00
solokot
b50c951091 Translated using Weblate (Russian)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-03-08 09:33:17 +01:00
Kunzisoft
2f589a95a9 Translated using Weblate (English)
Currently translated at 100.0% (426 of 426 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-03-08 09:33:15 +01:00
J-Jamet
53eac86a95 Update AndroidX dependencies and fix small warning 2020-03-08 09:17:18 +01:00
J-Jamet
9412f8955e Update kotlin to 1.3.61 2020-03-08 09:07:14 +01:00
J-Jamet
71c98d82b1 Update gradle to 5.6.4 2020-03-08 09:00:56 +01:00
J-Jamet
42c1a925b4 Upgrade app to 2.54beta28 2020-03-08 08:59:41 +01:00
J-Jamet
1e84534ffd Merge tag '2.5beta27' into develop
2.5beta27
2020-03-07 14:41:37 +01:00
J-Jamet
b087733e37 Merge branch 'release/2.5beta27' 2020-03-07 14:41:29 +01:00
J-Jamet
675efe29ac Fix unused string 2020-03-07 14:31:08 +01:00
J-Jamet
9833af8225 Update french translations 2020-03-07 14:13:10 +01:00
J-Jamet
790c624571 Fix strong html 2020-03-07 14:06:50 +01:00
J-Jamet
67a70a8453 Merge branch 'translations' into develop 2020-03-07 13:57:47 +01:00
J-Jamet
64bb05e2dd Update CHANGELOG 2020-03-07 13:29:09 +01:00
J-Jamet
c111db6e73 Rename preference keys 2020-03-07 13:25:53 +01:00
J-Jamet
24c5915bd3 Setting to request a search when opening a database #220 2020-03-07 13:18:45 +01:00
J-Jamet
04c3717618 Remove screenshot from bug report 2020-03-07 12:41:57 +01:00
J-Jamet
78275a0984 Fix search empty list 2020-03-07 12:37:46 +01:00
J-Jamet
bd908ed10d Update CHANGELOG 2020-03-07 12:01:03 +01:00
J-Jamet
5473deec95 Show URL before username when title is empty #463 2020-03-07 11:58:47 +01:00
Ettore Atalan
653258afd2 Translated using Weblate (German)
Currently translated at 99.7% (418 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-07 03:33:06 +01:00
J-Jamet
00a2210eea Fix button menu color 2020-03-06 17:36:02 +01:00
J-Jamet
ec5688a013 Remove autofill setting for incompatible devices 2020-03-06 17:29:48 +01:00
J-Jamet
fc0c7b5708 Update CHANGELOG 2020-03-06 15:54:02 +01:00
J-Jamet
464f7ac486 Sorted list and comparator improvement 2020-03-06 15:46:08 +01:00
solokot
fb2457146c Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-29 19:33:06 +01:00
solokot
d90870838e Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-28 19:15:38 +01:00
Andrew
8c45f23291 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-28 19:15:37 +01:00
Kunzisoft
315a3efa52 Added translation using Weblate (Romanian) 2020-02-27 14:01:57 +01:00
J-Jamet
28abb5ae6f Delete password from starting event #461 2020-02-27 13:26:41 +01:00
J-Jamet
bb78d89682 Try to fix aliased text #414 2020-02-27 13:17:01 +01:00
J-Jamet
fd36e19168 Update CHANGELOG 2020-02-27 12:57:31 +01:00
J-Jamet
58db516e44 Fix refresh appearance settings #452 #451 2020-02-27 12:54:47 +01:00
J-Jamet
2b875e94dc Add GZip string to attachment view if database is compressed 2020-02-27 12:38:16 +01:00
J-Jamet
0ba2447f55 Change about page 2020-02-27 10:16:43 +01:00
solokot
8e684d0d3a Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-25 23:32:46 +01:00
MrEnderfall
ce3c1d4685 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-25 23:32:46 +01:00
J-Jamet
2cd77d47eb Move up remember keyfile locations setting 2020-02-23 13:43:09 +01:00
J-Jamet
ac9366e351 Update CHANGELOGS 2020-02-23 13:39:57 +01:00
J-Jamet
af356586f8 Merge branch 'feature/Check_Broken_Link' into develop 2020-02-23 13:33:56 +01:00
J-Jamet
7650db81a4 Hide broken database links 2020-02-23 13:15:09 +01:00
J-Jamet
8925c86afd Fix remember key file after press back 2020-02-23 12:49:40 +01:00
J-Jamet
8f1836009e Fix database and keyfile locations settings 2020-02-23 12:39:39 +01:00
J-Jamet
5fcf3f9b95 Fix erasing file 2020-02-23 10:47:28 +01:00
Kunzisoft
1c466d9e40 Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-22 17:33:01 +01:00
WaldiS
b97c2d9cbc Translated using Weblate (Polish)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-02-22 17:33:01 +01:00
zeritti
3d970e4967 Translated using Weblate (Czech)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-02-22 17:33:01 +01:00
J-Jamet
765c8f53dd Remove permission variable and rename preference 2020-02-20 19:53:49 +01:00
J-Jamet
4e81caeadf Remove unused external storage permission for Sdk > 18 2020-02-20 19:41:48 +01:00
J-Jamet
08b10e4d58 Update string 2020-02-20 19:35:52 +01:00
J-Jamet
f8594f72e8 Fix small elements 2020-02-20 19:31:44 +01:00
J-Jamet
24ebee07cd Fix small elements 2020-02-20 19:26:03 +01:00
J-Jamet
a06ebb0991 Force read only when file is not writable 2020-02-20 19:20:00 +01:00
J-Jamet
cd5d4498e7 Fix small element 2020-02-20 16:11:50 +01:00
J-Jamet
0bd62780c6 Check if uri is broken 2020-02-20 14:13:11 +01:00
J-Jamet
896f9327d6 Fix error messages #431 2020-02-20 11:58:00 +01:00
J-Jamet
cc83a99efe Try to fix #465 2020-02-20 09:06:11 +01:00
J Smith
b6da20fef7 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-02-19 23:53:44 +01:00
Kunzisoft
2912678559 Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-19 23:53:38 +01:00
WaldiS
409e870ef0 Translated using Weblate (Polish)
Currently translated at 99.5% (417 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-02-19 23:53:38 +01:00
Kunzisoft
3d84074a0a Translated using Weblate (English)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-02-19 23:53:37 +01:00
Oğuz Ersen
a717fdfed4 Translated using Weblate (Turkish)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-02-19 23:53:36 +01:00
J-Jamet
be1f68015b Merge branch 'master' into develop 2020-02-17 19:05:23 +01:00
WaldiS
2c29dcf1f6 Translated using Weblate (Polish)
Currently translated at 99.0% (415 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-02-17 00:50:57 +01:00
Filip Miletic
2b1173177f Translated using Weblate (Croatian)
Currently translated at 79.4% (333 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-02-17 00:50:55 +01:00
jan madsen
3c0f7dc79c Translated using Weblate (Danish)
Currently translated at 98.8% (414 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-02-17 00:50:42 +01:00
marzzzello
39d813bf3a Translated using Weblate (German)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-02-17 00:50:40 +01:00
Filip Miletic
5e2bc0d05b Translated using Weblate (Croatian)
Currently translated at 51.3% (215 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-02-14 04:18:25 +01:00
iLocIT!
ec751159ae Translated using Weblate (German)
Currently translated at 89.5% (375 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-02-14 04:18:02 +01:00
Filip Miletic
dda8b95f83 Translated using Weblate (Croatian)
Currently translated at 11.0% (46 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-02-13 19:31:33 +01:00
Filip Miletic
286012fe2a Added translation using Weblate (Croatian) 2020-02-13 11:04:02 +01:00
Jérémy JAMET
ab27299789 Update description for other KeePass app 2020-02-12 17:09:14 +01:00
Éfrit
dfcdd5aa88 Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-11 21:50:45 +01:00
Allan Nordhøy
a05ea52689 Translated using Weblate (Italian)
Currently translated at 72.8% (305 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-02-11 21:50:45 +01:00
zeritti
de12b5de5b Translated using Weblate (Czech)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-02-11 21:50:44 +01:00
solokot
57b03eaca4 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-11 21:50:43 +01:00
Retrial
36bd00b760 Translated using Weblate (Greek)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-02-11 21:50:42 +01:00
Oğuz Ersen
4578a9974a Translated using Weblate (Turkish)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-02-11 21:50:33 +01:00
J-Jamet
d0371f58c6 Capture exception to avoid crash after clear clipboard 2020-02-08 17:39:08 +01:00
solokot
9f80457351 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-08 16:39:00 +01:00
J-Jamet
56daf6f676 Remove JVMOverload in ClipboardHelper 2020-02-08 12:09:12 +01:00
J-Jamet
7f7b8d423b Merge branch 'translations' into develop 2020-02-08 11:34:35 +01:00
J-Jamet
031afc80cb Fix contribution strings 2020-02-08 11:32:56 +01:00
J-Jamet
b77e28b04d Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-02-08 11:27:36 +01:00
Allan Nordhøy
2f8c3fdcfe Translated using Weblate (Norwegian Bokmål)
Currently translated at 81.4% (341 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-02-08 11:27:15 +01:00
solokot
f501a87099 Translated using Weblate (Russian)
Currently translated at 99.8% (418 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-08 11:27:14 +01:00
Kunzisoft
d0ec5f26dd Translated using Weblate (English)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-02-08 11:27:13 +01:00
Mesut Akcan
97776e9329 Translated using Weblate (Turkish)
Currently translated at 98.3% (412 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-02-08 11:27:11 +01:00
Kunzisoft
a19356c49e Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-08 11:21:45 +01:00
J-Jamet
4f16918cf0 Remove unused strings 2020-02-08 11:19:45 +01:00
J-Jamet
1af9761144 Merge branch 'master' into develop 2020-02-07 22:14:20 +01:00
J-Jamet
d74e814c79 Fix fastlane data 2020-02-07 22:05:58 +01:00
J-Jamet
16d09bca6c Merge tag '2.5.0.0beta26' into develop
2.5.0.0beta26
2020-02-07 21:45:51 +01:00
J-Jamet
462c29b769 Merge branch 'release/2.5.0.0beta26' 2020-02-07 21:45:40 +01:00
J-Jamet
3939276d58 Fix translations 2020-02-07 19:44:21 +01:00
J-Jamet
ff85f18c4c Update ReadMe.md 2020-02-07 19:24:12 +01:00
J-Jamet
dd14fe4123 Fix exception after changing theme 2020-02-07 19:20:43 +01:00
J-Jamet
d804659ee2 Change blue color of classic dark style 2020-02-07 19:10:48 +01:00
J-Jamet
e3152cf901 Remove AMOLED string for Black theme 2020-02-07 18:57:07 +01:00
J-Jamet
de236f321f Upgrade CHANGELOG 2020-02-07 18:26:27 +01:00
J-Jamet
66f4611866 Restore and delete entry history #335 2020-02-07 18:22:37 +01:00
heta1121
6a6ef052af Translated using Weblate (Swedish)
Currently translated at 94.9% (388 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-02-06 19:07:41 +01:00
J-Jamet
4e4606dabd Fix strings #436 2020-01-31 20:14:53 +01:00
J-Jamet
0ce11103ab Update CHANGELOG 2020-01-31 19:33:24 +01:00
J-Jamet
899d0e0557 Fix expired entries views 2020-01-31 19:29:40 +01:00
J-Jamet
4aefeff41f Hide expired entries from quick search when setting is activated #453 2020-01-31 17:26:36 +01:00
J-Jamet
08d59e50e8 Add android:resizeableActivity="true" for DeX mode 2020-01-31 16:48:15 +01:00
J-Jamet
e6c06aba8c Merge branch 'qtwyeuritoiy-feature/amoled_black' into develop 2020-01-31 13:12:37 +01:00
J-Jamet
e5c2a04922 Merge branch 'feature/amoled_black' of git://github.com/qtwyeuritoiy/KeePassDX into qtwyeuritoiy-feature/amoled_black 2020-01-31 13:12:18 +01:00
J-Jamet
c134ccf8d9 Encapsulate filters 2020-01-29 20:20:39 +01:00
J-Jamet
4f959d1ff6 Merge branch 'feature/hide_expired_entries' of git://github.com/qtwyeuritoiy/KeePassDX into qtwyeuritoiy-feature/hide_expired_entries 2020-01-29 17:22:40 +01:00
J-Jamet
1c06d93951 Fix custom fields in Magikeyboard 2020-01-29 17:10:26 +01:00
J-Jamet
b2dff29baa Auto performed "Go" key in Magikeyboard 2020-01-29 14:13:13 +01:00
J-Jamet
72633e9472 Change KEYCODE_ENTER by IME_ACTION_GO #369 2020-01-29 13:34:32 +01:00
J-Jamet
898a88f7d8 Fix app description 2020-01-27 12:52:46 +01:00
J-Jamet
c2f09f6666 Change "KeePass DX" name to "KeePassDX" and fix small issues #411 2020-01-27 12:43:45 +01:00
J-Jamet
d45bcbc347 Fix empty space in entry content 2020-01-27 11:18:54 +01:00
Jihoon Kim
1f834567f8 [Feature/HideExpiredEntries] Move 'Hide Expired Entries' Switch to 'Other' Category
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-27 17:39:26 +08:00
Jihoon Kim
0b62c04867 [Feature/AmoledBlack] Add AMOLED Black Theme
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-27 17:00:24 +08:00
J-Jamet
c71bb2a570 Password display box multiline #225 2020-01-26 18:02:11 +01:00
J-Jamet
0a575b5bf8 Prevent capture for all screens 2020-01-26 15:47:50 +01:00
J-Jamet
6f2bf903c5 Fix storage permission 2020-01-26 13:26:09 +01:00
Rodrigo Saldaña
210ad4b8db Translated using Weblate (Spanish)
Currently translated at 88.5% (362 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-01-25 19:21:30 +01:00
Allan Nordhøy
b42cf0e204 Translated using Weblate (Norwegian Bokmål)
Currently translated at 87.5% (358 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-01-23 09:21:33 +01:00
solokot
2045d3aab8 Translated using Weblate (Russian)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-01-23 09:21:32 +01:00
Allan Nordhøy
e20cb9431d Translated using Weblate (Danish)
Currently translated at 95.6% (391 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-01-23 09:21:31 +01:00
J-Jamet
7161eaea8b Allow write permission for specific file managers 2020-01-22 16:37:38 +01:00
J-Jamet
b786da52f5 Add error snackbar for error in entry edit view #431 2020-01-22 13:01:15 +01:00
J-Jamet
ccbfec838d Fix disclaimer formal string 2020-01-22 12:18:19 +01:00
J-Jamet
f5c1872225 Fix show read only warning 2020-01-22 10:53:24 +01:00
heta1121
3001013bad Translated using Weblate (Swedish)
Currently translated at 85.6% (350 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-01-21 17:21:30 +01:00
Allan Nordhøy
1280803e5e Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.9% (343 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-01-19 15:22:00 +01:00
heta1121
f84fc07fe0 Translated using Weblate (Swedish)
Currently translated at 85.1% (348 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-01-19 15:22:00 +01:00
J-Jamet
d83e53f589 Fix read only for recycle bin 2020-01-19 12:17:27 +01:00
J-Jamet
52ba2c53f1 Fix progress for Android < 5 2020-01-18 15:23:51 +01:00
J-Jamet
f266e1de4c Add string to open the attachment downloaded 2020-01-18 12:24:21 +01:00
J-Jamet
5a0aafd3ce Update CHANGELOG 2020-01-18 12:19:56 +01:00
J-Jamet
c7ce07a43c Change file size format 2020-01-18 12:15:14 +01:00
J-Jamet
a73644c285 Merge branch 'feature/Stream_Binaries' into develop 2020-01-18 12:03:55 +01:00
J-Jamet
e28f1ffc99 Fix attachment orientation change 2020-01-18 11:33:17 +01:00
Deleted User
afc691b2f4 Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.9% (343 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-01-16 06:27:50 +01:00
J-Jamet
00f6eb83c3 Fix close notification 2020-01-15 05:52:32 +01:00
J-Jamet
bbf51ebbec Multiple attachments download and add file compression 2020-01-14 19:44:10 +01:00
Jihoon Kim
7816c8f16e [Feature/HideExpiredEntries] Apply 'Hide Expired Entries' Setting And Adjust Item Count Accordingly
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-14 17:28:27 +08:00
Jihoon Kim
fdbcba2412 [Feature/HideExpiredEntries] Add Preference Entry for 'Hide Expired Entries'
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-14 17:28:27 +08:00
J-Jamet
50dc6cb0aa Fix download attachment 2020-01-13 10:05:34 +01:00
J-Jamet
e413198d12 Download attachment dynamic string 2020-01-10 19:40:00 +01:00
solokot
72592772f9 Translated using Weblate (Russian)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-01-09 09:21:27 +01:00
solokot
f918206bcd Translated using Weblate (Russian)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-01-07 22:27:33 +01:00
J-Jamet
6b34d67da8 Remove WRITE_EXTERNAL_STORAGE for Android > 18
Download attachment as service
2020-01-07 19:43:41 +01:00
Retrial
04fb093d4d Translated using Weblate (Greek)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-01-04 18:21:28 +01:00
J-Jamet
7de0e0cc4a Add Attachment view 2019-12-31 14:34:51 +01:00
J-Jamet
5cf7688e38 Merge branch 'develop' into feature/Stream_Binaries 2019-12-31 12:05:48 +01:00
J-Jamet
97cd06b52b Merge branch 'feature/Duplicate_UUID_Database1' into develop 2019-12-31 11:23:44 +01:00
J-Jamet
6d522ead1d Fix illegal argument exception and refactor KDB parser code 2019-12-31 08:26:15 +01:00
J-Jamet
8e0fae62f3 Merge branch 'develop' into feature/Stream_Binaries 2019-12-30 14:09:05 +01:00
J-Jamet
af72098d60 Refactor bytes utility methods 2019-12-30 12:59:50 +01:00
Taeko Hasegawa
62da582f4e Translated using Weblate (Japanese)
Currently translated at 36.4% (149 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2019-12-23 14:21:24 +01:00
J-Jamet
f3efa6eddc Add parameter to fix duplicate UUID in DatabaseV1 2019-12-22 18:26:35 +01:00
J-Jamet
c85fb7bd0a Better element output KDB implementation 2019-12-22 18:13:26 +01:00
J-Jamet
fc7b183461 Update CHANGELOG 2019-12-22 17:51:48 +01:00
J-Jamet
a378810f88 Improvement code for ClipboardHelper 2019-12-21 21:36:33 +01:00
J-Jamet
2f5b322fad Fix crash during clean clipboard in Android 9 2019-12-21 21:31:12 +01:00
J-Jamet
efb9b50f85 Fix issue #127 KDBX binaries 2019-12-21 21:18:58 +01:00
J-Jamet
84fdef8eb6 Merge branch 'develop' into feature/Stream_Binaries 2019-12-21 19:46:05 +01:00
J-Jamet
76050a261b Update to version 2.5.0.0beta26 2019-12-21 19:16:30 +01:00
Éfrit
2667361450 Translated using Weblate (French)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-12-19 17:18:38 +01:00
Éfrit
a2b3313cc0 Translated using Weblate (French)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-12-18 22:50:52 +01:00
J-Jamet
d343446235 Check file size to not corrupt a database v1 if too high 2019-12-18 18:58:26 +01:00
J-Jamet
cbce70f8a4 Save KDB binaries as file 2019-12-18 18:46:50 +01:00
WaldiS
4573d6b6fb Translated using Weblate (Polish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-12-18 14:21:21 +01:00
Chen Wang
973305d13c Translated using Weblate (Chinese (Simplified))
Currently translated at 99.3% (406 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-12-16 08:21:13 +01:00
solokot
4637016372 Translated using Weblate (Russian)
Currently translated at 99.8% (408 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-12-16 08:21:12 +01:00
Chen Wang
698ba4f7f1 Translated using Weblate (English)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-12-16 08:21:11 +01:00
Oğuz Ersen
502ebabf1f Translated using Weblate (Turkish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-12-16 08:21:10 +01:00
WaldiS
13f88626ca Translated using Weblate (Polish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-12-08 15:13:26 +01:00
zeritti
a39f58f7b5 Translated using Weblate (Czech)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2019-12-08 15:13:25 +01:00
nautilusx
aeb36468ce Translated using Weblate (German)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-12-08 15:13:23 +01:00
J-Jamet
fdd196526d Better InputStream / OutputStream close 2019-12-05 19:28:54 +01:00
Mattias Münster
ecca892b16 Translated using Weblate (Swedish)
Currently translated at 25.9% (106 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2019-12-04 02:05:37 +01:00
Rudah Ximenes Alvarenga
03343d0301 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-12-04 02:05:36 +01:00
Allan Nordhøy
a61c8b0cb6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.6% (342 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-12-02 03:33:26 +01:00
Kunzisoft
a92129da00 Translated using Weblate (French)
Currently translated at 99.5% (407 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-12-02 03:33:24 +01:00
Allan Nordhøy
0b783d6390 Translated using Weblate (English)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-12-02 03:33:23 +01:00
jan madsen
9aa1a2e30e Translated using Weblate (Danish)
Currently translated at 95.8% (392 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2019-12-02 03:33:23 +01:00
C. Rüdinger
15b3c69514 Translated using Weblate (German)
Currently translated at 99.5% (407 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-12-02 03:33:21 +01:00
Oğuz Ersen
f2722e09ec Translated using Weblate (Turkish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-12-02 03:33:21 +01:00
Deleted User
b442859be0 Translated using Weblate (German)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-12-01 17:45:31 +01:00
J-Jamet
1496a2efb1 Fix database decryption with attachment 2019-11-30 19:26:26 +01:00
J-Jamet
a0edb111f0 Merge branch 'develop' into feature/Stream_Binaries 2019-11-30 18:27:21 +01:00
J-Jamet
6bcf54fe29 Merge tag '2.5.0.0beta25' into develop
2.5.0.0beta25
2019-11-30 17:43:19 +01:00
J-Jamet
3d7e24816a Merge branch 'release/2.5.0.0beta25' 2019-11-30 17:43:05 +01:00
J-Jamet
f5e02ec63f Merge branch 'translations' into develop 2019-11-30 17:27:39 +01:00
J-Jamet
ed1f4ebace Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2019-11-30 17:27:20 +01:00
J-Jamet
eb0e496cfd Try to decrypt DatabaseV1 by InputStream 2019-11-30 17:02:09 +01:00
J-Jamet
8b9fc30a6d Upgrade description 2019-11-30 10:12:43 +01:00
J-Jamet
12c2a6e99c Refactor bytes utility methods 2019-11-29 14:19:13 +01:00
J-Jamet
714433b62d Refactor stream methods 2019-11-28 16:32:16 +01:00
J-Jamet
e42933d786 Move SortNodeEnum 2019-11-27 22:49:59 +01:00
J-Jamet
e9531e4edd Merge branch 'feature/Refactor_Class' into develop 2019-11-27 19:21:36 +01:00
J-Jamet
0cd9cd294d Move database elements in packages 2019-11-27 19:19:42 +01:00
J-Jamet
b03fb12fca Refactor Input and Output 2019-11-27 18:59:09 +01:00
J-Jamet
b7b2e8dc4e Change class by KDB and KDBX 2019-11-27 18:51:28 +01:00
J-Jamet
96568abc51 Update CHANGELOG 2019-11-27 17:36:40 +01:00
J-Jamet
a180688858 Merge branch 'feature/OOM' into develop 2019-11-27 17:34:44 +01:00
J-Jamet
2590067558 Fix exception message 2019-11-27 17:34:26 +01:00
J-Jamet
b5499238f7 Change binary attachment compression after change compression setting 2019-11-27 16:46:14 +01:00
J-Jamet
cc5b96f539 Fix OOM by stream implementation and add KDBX version for DatabaseV2 2019-11-26 17:21:33 +01:00
J-Jamet
32343dc937 Refactor binary class 2019-11-26 11:07:29 +01:00
J-Jamet
1e71dd3031 Better binary reader 2019-11-25 21:14:39 +01:00
J-Jamet
ebf6f6a52a Replace Base64 lib 2019-11-25 19:25:08 +01:00
J-Jamet
6bf6d661af New databaseV2 XML write methods 2019-11-25 18:20:02 +01:00
J-Jamet
fe16879f35 New method to clear the clipboard 2019-11-25 15:12:27 +01:00
J-Jamet
ead384d1bb Update invalid credentials error message 2019-11-25 14:20:28 +01:00
J-Jamet
1ebdc0bacd Fix copy and move entry crash in root for DatabaseV1 2019-11-25 14:17:07 +01:00
zeritti
8eca8cdd53 Translated using Weblate (Czech)
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2019-11-25 14:04:59 +01:00
J-Jamet
24c61b1b37 Partial fix of Database V1 node deletion #394 2019-11-25 13:59:32 +01:00
J-Jamet
ea18cc7166 Remove unused element 2019-11-21 18:09:54 +01:00
J-Jamet
387c499829 Fix expires for new entry in DatabaseV1 2019-11-21 17:01:57 +01:00
J-Jamet
339470dd6e Fix open button 2019-11-21 16:15:21 +01:00
J-Jamet
02d1089dbc Better biometric state management 2019-11-21 13:12:38 +01:00
J-Jamet
1bc0932cec Fix fingerprint animation 2019-11-21 12:13:08 +01:00
J-Jamet
76cc2739c8 Change biometric prompt messages 2019-11-21 11:58:59 +01:00
J-Jamet
b23908aec2 Change biometric error 2019-11-21 10:50:49 +01:00
J-Jamet
6b1b8c0f7b Add biometric error message 2019-11-21 10:44:22 +01:00
J-Jamet
06320a7eba Add sw600dp-port dimen 2019-11-21 10:13:04 +01:00
WaldiS
fc02145d0c Translated using Weblate (Polish)
Currently translated at 99.7% (398 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-11-21 10:04:56 +01:00
Gaurav Sehar
5360738775 Translated using Weblate (Hindi)
Currently translated at 23.3% (93 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hi/
2019-11-21 10:04:54 +01:00
J-Jamet
0957df752a Fix creating database 2019-11-21 10:04:34 +01:00
J-Jamet
fe56d06b5d Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2019-11-21 09:16:34 +01:00
J-Jamet
f4955b16cd Remove specific ndk version to avoid error on FDroid 2019-11-20 23:10:56 +01:00
J-Jamet
07692ba73d Implicit confirmation for biometric #390 2019-11-20 19:36:05 +01:00
J-Jamet
64ac3e8f32 Change reader header and data equals 2019-11-20 16:06:59 +01:00
J-Jamet
8013d3109a Small code change 2019-11-20 12:55:14 +01:00
J-Jamet
be6f01dc99 Fix PWDate and blocks 2019-11-20 12:40:56 +01:00
Mattias Münster
30f7779ec6 Translated using Weblate (Swedish)
Currently translated at 26.8% (107 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2019-11-20 00:04:47 +01:00
solokot
6b5823ca70 Translated using Weblate (Russian)
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-11-20 00:04:45 +01:00
J-Jamet
d4a09ed569 Merge branch 'feature/Manually_Save_Satabase' into develop 2019-11-19 19:18:00 +01:00
J-Jamet
84fe409c4b Nested fragment for general and database settings 2019-11-18 21:18:34 +01:00
J-Jamet
51d90891ad Fix auto save database 2019-11-18 19:59:50 +01:00
J-Jamet
b1fa06099c Refactor action database and add new execution command string 2019-11-18 19:55:17 +01:00
J-Jamet
fa9d8ad6ad Refactor progress dialog thread / save database menu 2019-11-18 17:54:13 +01:00
Azurak
6703d7b451 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-11-17 02:04:55 +01:00
Azurak
73afd44d9c Translated using Weblate (Chinese (Traditional))
Currently translated at 48.4% (193 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2019-11-17 02:04:53 +01:00
Stephan Paternotte
93cb93bb9b Translated using Weblate (Dutch)
Currently translated at 99.2% (396 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2019-11-17 02:04:49 +01:00
WaldiS
82902cff71 Translated using Weblate (Polish)
Currently translated at 95.7% (382 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-11-17 02:04:46 +01:00
Azurak
3657f7e54c Translated using Weblate (English)
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-11-17 02:04:44 +01:00
Kunzisoft
a57a2f738d Translated using Weblate (English)
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-11-17 02:04:44 +01:00
jan madsen
b93592d703 Translated using Weblate (Danish)
Currently translated at 96.0% (383 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2019-11-17 02:04:44 +01:00
C. Rüdinger
fd288e624b Translated using Weblate (German)
Currently translated at 99.7% (398 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-11-17 02:04:42 +01:00
Rudah Ximenes Alvarenga
095fa3f6ef Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-11-17 02:04:41 +01:00
Oğuz Ersen
b1f6c578ad Translated using Weblate (Turkish)
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-11-17 02:04:37 +01:00
Mesut Akcan
1bc991bfcb Translated using Weblate (Turkish)
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-11-17 02:04:32 +01:00
J-Jamet
02feb478e8 Add autosave database setting 2019-11-16 20:18:07 +01:00
J-Jamet
c2df79f0c9 Fix save database in read only mode 2019-11-16 19:36:29 +01:00
J-Jamet
ef13965747 Fix save database in read only mode 2019-11-16 18:34:39 +01:00
Kunzisoft
13421601de Translated using Weblate (French)
Currently translated at 99.7% (398 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-11-16 11:53:24 +01:00
Azurak
5a9b46c4b5 Translated using Weblate (Chinese (Traditional))
Currently translated at 33.3% (133 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2019-11-15 18:31:47 +01:00
Thomas
6268642097 Translated using Weblate (Chinese (Traditional))
Currently translated at 33.3% (133 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2019-11-15 18:31:45 +01:00
J-Jamet
12eadbc092 Update CHANGELOG 2019-11-14 16:35:23 +01:00
J-Jamet
c9475d1dc2 Fix concurrent exception when update group 2019-11-14 16:28:44 +01:00
J-Jamet
56805defb6 Merge branch 'feature/RecyclerBin' into develop 2019-11-14 15:57:09 +01:00
J-Jamet
477a784201 Empty recycle bin 2019-11-14 15:56:30 +01:00
J-Jamet
f54bac15c9 Add recycle bin setting and fix delete node 2019-11-14 13:14:02 +01:00
J-Jamet
ae28ebe701 Add dialog when permanently delete nodes 2019-11-14 09:59:55 +01:00
oskamuelller4fs
f16adf00da Translated using Weblate (German)
Currently translated at 100.0% (399 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-11-14 03:20:56 +01:00
C. Rüdinger
d17699f6f7 Translated using Weblate (German)
Currently translated at 96.2% (384 of 399 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-11-14 01:43:58 +01:00
J-Jamet
8afcf043ee Fix database name 2019-11-13 20:06:44 +01:00
J-Jamet
dda9d034aa Same feature as KeePass2 for entry history 2019-11-13 19:17:36 +01:00
J-Jamet
652bad51b4 Fix infinite dialog after entry is save #387 2019-11-13 18:54:04 +01:00
J-Jamet
4d2ccc0789 Remove unused onPreferenceClick 2019-11-13 17:42:50 +01:00
J-Jamet
0e1c21e0f4 Small code change 2019-11-13 17:31:38 +01:00
J-Jamet
8d3f58b2cc Merge branch 'feature/RefactorImporter' into develop 2019-11-13 17:29:30 +01:00
J-Jamet
be78905d85 Rename input output utility class for db 2019-11-13 17:04:41 +01:00
J-Jamet
b3f232c840 Upgrade Importer V3 2019-11-13 15:57:52 +01:00
J-Jamet
075a16c1c3 Remove OTP menu from DatabaseV1 2019-11-13 12:58:10 +01:00
J-Jamet
18a13e6266 Fix OTP base32 length and remove spaces 2019-11-13 12:46:52 +01:00
Jérémy JAMET
7149bdbc3a Update bug_report.md 2019-11-12 20:03:19 +01:00
J-Jamet
9a4c4aa9bf Fix string bugs 2019-11-12 17:38:08 +01:00
Hosted Weblate
2b32cab9d1 Merge branch 'origin/master' into Weblate. 2019-11-12 17:06:11 +01:00
J-Jamet
66611db261 Fix TOTP secret uppercase #381 2019-11-12 15:45:40 +01:00
J-Jamet
fdc2095b42 Remove access view 2019-11-12 15:38:12 +01:00
J-Jamet
2f9cab0da2 Upgrade to 2.5.0.0beta25 2019-11-12 13:21:21 +01:00
J-Jamet
bd0d474751 Upgrade Pro description 2019-11-12 13:08:47 +01:00
J-Jamet
cca0ab2669 Merge tag '2.5.0.0beta24' into develop
2.5.0.0beta24
2019-11-12 12:35:28 +01:00
J-Jamet
cb5ca575d5 Merge branch 'release/2.5.0.0beta24' 2019-11-12 12:35:16 +01:00
J-Jamet
f4caaad9ee Fix invalid_db_same_uuid error 2019-11-12 12:09:58 +01:00
J-Jamet
b9cfb32a20 Fix OTP dialog 2019-11-12 11:56:19 +01:00
J-Jamet
095e5e5dd6 Add FLAG_GRANT_PREFIX_URI_PERMISSION flag 2019-11-12 10:43:32 +01:00
J-Jamet
ffc58688d8 Upgrade Gradle 2019-11-12 09:38:36 +01:00
Allan Nordhøy
6a69a7f416 Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.5% (360 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-11-11 23:04:19 +01:00
J-Jamet
b4188b4712 Fix small bugs 2019-11-11 16:50:05 +01:00
J-Jamet
4a22c28df4 Merge branch 'translations' into develop 2019-11-11 16:34:39 +01:00
J-Jamet
76e9a25b1a Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2019-11-11 16:33:54 +01:00
J-Jamet
1928d0823e Fix small french translations 2019-11-11 14:47:57 +01:00
J-Jamet
c183d22412 Upgrade Biometric library 2019-11-11 14:44:38 +01:00
J-Jamet
b684353721 Upgrade CHANGELOG and Readme.md 2019-11-11 14:19:29 +01:00
J-Jamet
72f0e871c7 Fix close clipboard notification 2019-11-11 14:09:41 +01:00
J-Jamet
9a63962903 Fix clipboard notification with auto generated field 2019-11-11 14:00:30 +01:00
J-Jamet
938de28b49 Minimized hidden text #373 2019-11-10 16:30:22 +01:00
J-Jamet
20fc094d71 Steam OTP on closed and full version 2019-11-10 15:26:52 +01:00
J-Jamet
40180d5883 Merge branch 'feature/TOTP' into develop 2019-11-10 14:33:35 +01:00
J-Jamet
59e5865318 Fix OTP errors 2019-11-10 14:31:21 +01:00
J-Jamet
f63d6bdc1d Fix OTP orientation change listener 2019-11-10 14:10:36 +01:00
J-Jamet
fe33c0ae7d Fix init OTP elements 2019-11-10 13:20:19 +01:00
J-Jamet
ca4ad1c1fd Validate OTP dialog only if no error 2019-11-10 12:19:06 +01:00
J-Jamet
adf5382804 Add min and max values for OTP 2019-11-10 11:25:34 +01:00
J-Jamet
7f5406ac98 OTP errors implementation 2019-11-09 15:19:14 +01:00
J-Jamet
23b21ea154 Fix OTP dialog 2019-11-08 19:36:59 +01:00
Antonio F
49d4d0421a Translated using Weblate (Spanish)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2019-11-08 18:04:10 +01:00
J-Jamet
23859a61bb Build OTP url 2019-11-07 19:28:54 +01:00
J-Jamet
221f81f51e Change ObjectNameResource and add OTP Dialog 2019-11-07 15:18:57 +01:00
J-Jamet
6e7c0d5073 Upgrade NDK version 2019-11-07 12:31:39 +01:00
Rudah Ximenes Alvarenga
e8e3d53685 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-11-06 21:04:12 +01:00
J-Jamet
e6d9df2b98 Add OTP auto generate field for Magikeyboard 2019-11-06 13:39:19 +01:00
J-Jamet
477a8f2e38 Update OTP code with AndOTP and kotlinized OtpEntryFields 2019-11-05 19:57:20 +01:00
J-Jamet
5e66697b8b Fix TOTP retrievement and add HOTP fields 2019-11-05 17:30:27 +01:00
J-Jamet
16320abb7d Global OTP variables, add progress bar, remove seconds 2019-11-05 16:04:30 +01:00
J-Jamet
f122c2832c Add OTP and fix TOTP generation 2019-11-05 13:42:51 +01:00
J-Jamet
d84c561f44 Merge branch 'studio315b-totpReadMode' into feature/TOTP 2019-11-04 18:23:17 +01:00
J-Jamet
da49c9c045 Merge branch 'totpReadMode' of git://github.com/studio315b/KeePassDX into studio315b-totpReadMode 2019-11-04 17:52:14 +01:00
J-Jamet
553920e37c Add setting to enable persistent notification 2019-11-03 11:29:28 +01:00
J-Jamet
450d2d113b Fix stop notification when edit entry 2019-11-03 10:55:47 +01:00
WaldiS
744c80e34d Translated using Weblate (Polish)
Currently translated at 99.7% (380 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-11-01 21:03:53 +01:00
J-Jamet
c0f8cca7c6 Fix history copy 2019-11-01 15:43:08 +01:00
J-Jamet
b129f220f7 Move copy button to the top #374 2019-11-01 15:09:41 +01:00
J-Jamet
7a3df02e38 Add explanation for settings 2019-10-30 21:40:53 +01:00
J-Jamet
befd29c396 Remove Magikeyboard explanation 2019-10-30 21:07:07 +01:00
J-Jamet
b8245621ea Remove fingerprint references 2019-10-30 20:19:33 +01:00
J-Jamet
ecda25a743 Fix biometric during orientation change 2019-10-30 19:36:52 +01:00
J-Jamet
d97a85b997 Remove biometric link if change credential 2019-10-30 15:21:50 +01:00
J-Jamet
8c0d7ab9ed Better Action runnable implementation 2019-10-30 14:49:40 +01:00
Allan Nordhøy
f3fa73ea34 Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.5% (360 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-10-30 11:03:44 +01:00
Philipp Fischbeck
788734ccad Translated using Weblate (German)
Currently translated at 98.4% (375 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-10-30 11:03:43 +01:00
J-Jamet
e088f4a4ad Fix opening database after register biometric 2019-10-30 10:16:27 +01:00
J-Jamet
86bd018e4e Validate preference dialog with virtual done keyboard button 2019-10-29 11:25:20 +01:00
J-Jamet
283145034d Fix preference number 2019-10-29 10:50:09 +01:00
J-Jamet
163162497e Update libraries 2019-10-29 10:34:02 +01:00
J-Jamet
56911fb58f Refresh number of children after an action 2019-10-29 10:29:06 +01:00
J-Jamet
dae6481aff Update CHANGELOG 2019-10-29 10:16:31 +01:00
J-Jamet
6b2eb5e4f6 Merge branch 'feature/Database_Notification' into develop 2019-10-29 10:13:23 +01:00
J-Jamet
c563787f73 Change notification icons 2019-10-29 10:09:46 +01:00
J-Jamet
2737755b85 Change notification icons 2019-10-29 09:38:01 +01:00
Trygve John
fd9486ca77 Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.5% (360 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-10-29 09:03:19 +01:00
J-Jamet
14020ec0b5 Fix clipboard notification timeout 2019-10-28 12:20:22 +01:00
J-Jamet
5a6c466ebd Add notification for opened database and use compat notification manager 2019-10-28 11:50:06 +01:00
WaldiS
76fcd5fe19 Translated using Weblate (Polish)
Currently translated at 99.2% (378 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-10-26 19:53:28 +02:00
J-Jamet
3732ff1ebc Fix clean clipboard after killing app by swap 2019-10-26 08:58:36 +02:00
Aykut ÖZDEMİR
22dd09954b Translated using Weblate (Turkish)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-10-25 13:53:16 +02:00
J-Jamet
ef7387f2f3 Remove entry notifications after killing app (by swap) 2019-10-25 10:08:39 +02:00
J-Jamet
f774298587 Update CHANGELOG 2019-10-24 15:23:52 +02:00
Éfrit
21c6ea73b2 Translated using Weblate (French)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-10-22 23:53:07 +02:00
C. Rüdinger
51dc302bb0 Translated using Weblate (German)
Currently translated at 97.9% (373 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-10-22 23:53:06 +02:00
villabunterkunt
fb023b81b5 Translated using Weblate (German)
Currently translated at 97.9% (373 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-10-21 21:02:16 +02:00
Allan Nordhøy
9fd342f1e7 Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.5% (341 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-10-21 02:53:03 +02:00
abidin toumi
8988f17765 Translated using Weblate (Arabic)
Currently translated at 71.1% (271 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-10-19 10:52:58 +02:00
Adolfo Jayme Barrientos
b7dc13d863 Translated using Weblate (Spanish)
Currently translated at 89.8% (342 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2019-10-12 16:52:53 +02:00
Ema Panz
6e00fa2d01 Translated using Weblate (Italian)
Currently translated at 95.5% (364 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2019-10-09 15:58:47 +02:00
Jérémy JAMET
2573434763 Update Readme.md
update FAQ
2019-10-07 09:33:21 +02:00
Jérémy JAMET
f153c26fef Set theme jekyll-theme-cayman 2019-10-07 09:15:48 +02:00
Ldm Public
125f461cbe Translated using Weblate (French)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-10-06 16:56:46 +02:00
WaldiS
b705b4b712 Translated using Weblate (Polish)
Currently translated at 99.2% (378 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-10-06 16:56:45 +02:00
Retrial
c67b0bb858 Translated using Weblate (Greek)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2019-10-06 16:56:45 +02:00
Kunzisoft
c4d3c8cbfb Translated using Weblate (French)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-10-04 18:37:57 +02:00
J Smith
4e16ba5f56 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-10-01 11:56:16 +02:00
abidin toumi
7137a2fadb Translated using Weblate (Arabic)
Currently translated at 71.1% (271 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-10-01 11:56:14 +02:00
ButterflyOfFire
9d90d0eaba Translated using Weblate (Arabic)
Currently translated at 71.1% (271 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-10-01 11:56:12 +02:00
WaldiS
0aa0b3e993 Translated using Weblate (Polish)
Currently translated at 96.1% (366 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-09-25 20:28:03 +02:00
WaldiS
f6985c8944 Translated using Weblate (Polish)
Currently translated at 96.1% (366 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-09-23 15:28:06 +02:00
zeritti
4388d56c52 Translated using Weblate (Czech)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2019-09-23 15:28:05 +02:00
solokot
a70fe24c97 Translated using Weblate (Russian)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-09-23 15:28:02 +02:00
Avgerinos Panagiotis
8e0392753c Translated using Weblate (Greek)
Currently translated at 19.7% (75 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2019-09-23 15:28:01 +02:00
solokot
b0ec4942bc Translated using Weblate (Russian)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-09-19 04:51:14 +02:00
tinect
2cbc9675f6 Translated using Weblate (German)
Currently translated at 97.1% (370 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-09-19 04:51:11 +02:00
solokot
94bdb0e3da Translated using Weblate (Russian)
Currently translated at 99.7% (380 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-09-17 15:41:33 +02:00
Ldm Public
65360c2a1e Translated using Weblate (French)
Currently translated at 97.6% (372 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-09-16 22:24:14 +02:00
jan madsen
70d30bdbe6 Translated using Weblate (Danish)
Currently translated at 96.6% (368 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2019-09-16 22:24:13 +02:00
Mesut Akcan
66f7e6d1b1 Translated using Weblate (Turkish)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-09-16 22:24:11 +02:00
Hosted Weblate
5e5baa4892 Merge branch 'origin/master' into Weblate. 2019-09-15 10:17:14 +02:00
WaldiS
9d1257ed9d Translated using Weblate (Polish)
Currently translated at 97.6% (368 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-09-15 10:17:08 +02:00
Mesut Akcan
9d7546053d Translated using Weblate (Turkish)
Currently translated at 100.0% (377 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-09-15 10:17:07 +02:00
somkun
9558fcaf21 Add Read support for TOTP Tokens 2018-09-16 22:11:23 -07:00
551 changed files with 24235 additions and 14719 deletions

View File

@@ -20,9 +20,6 @@ Steps to reproduce the behavior:
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
** Keepass Database ** ** Keepass Database **
- Created with: [e.g Windows KeePass 2.42] - Created with: [e.g Windows KeePass 2.42]
- Version: [e.g. 2] - Version: [e.g. 2]
@@ -30,7 +27,7 @@ If applicable, add screenshots to help explain your problem.
- Size: [e.g. 150Mo] - Size: [e.g. 150Mo]
- Contains attachment: [e.g. Yes] - Contains attachment: [e.g. Yes]
**KeePass DX (please complete the following information):** **KeePassDX (please complete the following information):**
- Version: [e.g. 2.5.0.0beta23] - Version: [e.g. 2.5.0.0beta23]
- Build: [e.g. Free] - Build: [e.g. Free]
- Language: [e.g. French] - Language: [e.g. French]
@@ -38,7 +35,7 @@ If applicable, add screenshots to help explain your problem.
**Android (please complete the following information):** **Android (please complete the following information):**
- Device: [e.g. GalaxyS8] - Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1] - Version: [e.g. 8.1]
- Browser: [e.g. Chrome]
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

7
.gitignore vendored
View File

@@ -38,6 +38,13 @@ proguard/
# Android Studio captures folder # Android Studio captures folder
captures/ captures/
# Eclipse/VS Code
.project
.settings/*
*/.project
*/.classpath
*/.settings/*
# Intellij # Intellij
*.iml *.iml
.idea/workspace.xml .idea/workspace.xml

120
CHANGELOG
View File

@@ -1,14 +1,72 @@
KeepassDX (2.5.0.0beta24) KeePassDX(2.5beta31)
* 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)
* Fix Lock after screen off (wait 1.5 seconds)
* Upgrade autofill algorithm
* Fix ANR during file verifications
KeePassDX(2.5beta29)
* Upgrade autofill algorithm
* Delete registered KeyFile after save new credentials
* Fix title and username entry view refresh after an update
* Fix database lock request (open notification always active)
* Allow empty title in entries
* Add expiration datetime
KeePassDX(2.5beta28)
* Fix read only database
* Upgrade to Android SDK 29
KeePassDX (2.5beta27)
* New setting to hide broken links
* Show URL when title is empty
* Setting to open search field at database opening
* Fix settings for database locations
* Fix error message when database file not writable
* Fix appearance refresh settings
* Sort optimization
KeePassDX (2.5.0.0beta26)
* Download attachments
* Change file size string format
* Prevent screenshot for all screen
* Auto performed "Go" key in Magikeyboard
* Restore and delete entry history
* Setting to hide expired entries
* New Black theme
* Fix crash when clearing clipboard
* Fix attachments compressions
* Fix dates
* Fix UUID message for Database v1
KeePassDX (2.5.0.0beta25)
* Setting for Recycle Bin
* Fix Recycle bin issues
* Fix TOTP
* Fix infinite save
* Fix update group
* Fix OOM
KeePassDX (2.5.0.0beta24)
* Add OTP (HOTP / TOTP)
* Add settings (Color, Security, Master Key) * Add settings (Color, Security, Master Key)
* Show history of each entry * Show history of each entry
* Auto repair database for nodes with same UUID * Auto repair database for nodes with same UUID
* Management of expired nodes * Management of expired nodes
* Multi-selection for actions (Cut - Copy - Delete) * Multi-selection for actions (Cut - Copy - Delete)
* Fix settings * Open/Save database as service / Add persistent notification
* Fix edit group * Fix settings / edit group / small bugs
* Fix small bugs
KeepassDX (2.5.0.0beta23) KeePassDX (2.5.0.0beta23)
* New, more secure database creation workflow * New, more secure database creation workflow
* Recognize more database files * Recognize more database files
* Add alias for history files (WARNING: history is erased) * Add alias for history files (WARNING: history is erased)
@@ -17,14 +75,14 @@ KeepassDX (2.5.0.0beta23)
* Fix OOM with KeyFile * Fix OOM with KeyFile
* Fix small issues * Fix small issues
KeepassDX (2.5.0.0beta22) KeePassDX (2.5.0.0beta22)
* Rebuild code for actions * Rebuild code for actions
* Add UUID as entry view * Add UUID as entry view
* Fix bug with natural order * Fix bug with natural order
* Fix number of entries in databaseV1 * Fix number of entries in databaseV1
* New entry views * New entry views
KeepassDX (2.5.0.0beta21) KeePassDX (2.5.0.0beta21)
* Fix nested groups no longer visible in V1 databases * Fix nested groups no longer visible in V1 databases
* Improved data import algorithm for V1 databases * Improved data import algorithm for V1 databases
* Add natural database sort * Add natural database sort
@@ -32,10 +90,10 @@ KeepassDX (2.5.0.0beta21)
* Fix button disabled with only KeyFile * Fix button disabled with only KeyFile
* Show the number of entries in a group * Show the number of entries in a group
KeepassDX (2.5.0.0beta20) KeePassDX (2.5.0.0beta20)
* Fix a major bug that displays an entry history * Fix a major bug that displays an entry history
KeepassDX (2.5.0.0beta19) KeePassDX (2.5.0.0beta19)
* Add lock button always visible * Add lock button always visible
* New connection workflow * New connection workflow
* Code refactored in Kotlin * Code refactored in Kotlin
@@ -46,7 +104,7 @@ KeepassDX (2.5.0.0beta19)
* Fix memory when load database * Fix memory when load database
* Fix small bugs * Fix small bugs
KeepassDX (2.5.0.0beta18) KeePassDX (2.5.0.0beta18)
* New recent databases views * New recent databases views
* New information dialog * New information dialog
* Custom fields for the Magikeyboard * Custom fields for the Magikeyboard
@@ -55,10 +113,10 @@ KeepassDX (2.5.0.0beta18)
* Fix memory when opening the database * Fix memory when opening the database
* Memory management for attachments * Memory management for attachments
KeepassDX (2.5.0.0beta17) KeePassDX (2.5.0.0beta17)
* Fix font and search * Fix font and search
KeepassDX (2.5.0.0beta16) KeePassDX (2.5.0.0beta16)
* New search in a single fragment * New search in a single fragment
* Search suggestions * Search suggestions
* Added the display of usernames * Added the display of usernames
@@ -66,20 +124,20 @@ KeepassDX (2.5.0.0beta16)
* Fix read-only mode * Fix read-only mode
* Fix parcelable / toolbar / back * Fix parcelable / toolbar / back
KeepassDX (2.5.0.0beta15) KeePassDX (2.5.0.0beta15)
* Read only mode * Read only mode
* Best group recovery for the navigation fragment * Best group recovery for the navigation fragment
* Fix copies in notifications * Fix copies in notifications
* Fix orientation * Fix orientation
* Added translations * Added translations
KeepassDX (2.5.0.0beta14) KeePassDX (2.5.0.0beta14)
* Optimize all the memory with parcelables / fix search * Optimize all the memory with parcelables / fix search
KeepassDX (2.5.0.0beta13) KeePassDX (2.5.0.0beta13)
* Fix memory issue with parcelable (crash in beta12 version) * Fix memory issue with parcelable (crash in beta12 version)
KeepassDX (2.5.0.0beta12) KeePassDX (2.5.0.0beta12)
* Added the Magikeyboard to fill the forms (settings still in development) * Added the Magikeyboard to fill the forms (settings still in development)
* Added move and copy for groups and entries * Added move and copy for groups and entries
* New navigation in a single screen / new animations between activities * New navigation in a single screen / new animations between activities
@@ -92,10 +150,10 @@ KeepassDX (2.5.0.0beta12)
* Fix the fingerprint recognition (WARNING : The keystore is reinit, you must delete the old keys) * Fix the fingerprint recognition (WARNING : The keystore is reinit, you must delete the old keys)
* Fix small bugs * Fix small bugs
KeepassDX (2.5.0.0beta11) KeePassDX (2.5.0.0beta11)
* Fix crash in beta10 version * Fix crash in beta10 version
KeepassDX (2.5.0.0beta10) KeePassDX (2.5.0.0beta10)
* Dynamically change Algorithm and Key Derivation Function in settings * Dynamically change Algorithm and Key Derivation Function in settings
* Upgrade translations * Upgrade translations
* New red volcano theme, fix classic dark theme * New red volcano theme, fix classic dark theme
@@ -103,7 +161,7 @@ KeepassDX (2.5.0.0beta10)
* Update fingerprint state with checkbox * Update fingerprint state with checkbox
* Fix bugs * Fix bugs
KeepassDX (2.5.0.0beta9) KeePassDX (2.5.0.0beta9)
* Education Screens to learn how to use the app * Education Screens to learn how to use the app
* New designs * New designs
* New custom font for character visibility * New custom font for character visibility
@@ -112,9 +170,9 @@ KeepassDX (2.5.0.0beta9)
* Change setting organisation * Change setting organisation
* Pro version * Pro version
KeepassDX (2.5.0.0beta8) KeePassDX (2.5.0.0beta8)
* Hide custom entries protected * Hide custom entries protected
* Best management of field references (https://keepass.info/help/base/fieldrefs.html) * Best management of field references (https://KeePass.info/help/base/fieldrefs.html)
* Change database / default settings * Change database / default settings
* Add Autofill for search * Add Autofill for search
* Add sorting by last access and by creation time * Add sorting by last access and by creation time
@@ -122,7 +180,7 @@ KeepassDX (2.5.0.0beta8)
* Refactor old code * Refactor old code
* Fix bugs * Fix bugs
KeepassDX (2.5.0.0beta7) KeePassDX (2.5.0.0beta7)
* Rebuild Notifications * Rebuild Notifications
* Change links to https * Change links to https
* Add extended Ascii (ñæËÌÂÝÜ...) * Add extended Ascii (ñæËÌÂÝÜ...)
@@ -131,10 +189,10 @@ KeepassDX (2.5.0.0beta7)
* Add setting to prevent the password copy * Add setting to prevent the password copy
* Fix bugs * Fix bugs
KeepassDX (2.5.0.0beta6) KeePassDX (2.5.0.0beta6)
* Fix crash * Fix crash
KeepassDX (2.5.0.0beta5) KeePassDX (2.5.0.0beta5)
* Autofill (Android O) * Autofill (Android O)
* Deletion for group * Deletion for group
* New sorts with (Asc/Dsc, Groups before or after) * New sorts with (Asc/Dsc, Groups before or after)
@@ -155,7 +213,7 @@ KeepassDX (2.5.0.0beta5)
* Fix many small bugs * Fix many small bugs
* Add recycle bin setting (not yet accessible) * Add recycle bin setting (not yet accessible)
KeepassDX (2.5.0.0beta4) KeePassDX (2.5.0.0beta4)
* Show only file name * Show only file name
* Setting for full path * Setting for full path
* Add information for each database file * Add information for each database file
@@ -164,7 +222,7 @@ KeepassDX (2.5.0.0beta4)
* Delete view assignment for fingerprint opening * Delete view assignment for fingerprint opening
* Merge KeePassDroid 2.2.1 * Merge KeePassDroid 2.2.1
KeepassDX (2.5.0.0beta3) KeePassDX (2.5.0.0beta3)
* New database workflow with new screens and folder selection * New database workflow with new screens and folder selection
* Settings for default password generation * Settings for default password generation
* Fingerprint dialog for explanations * Fingerprint dialog for explanations
@@ -175,17 +233,17 @@ KeepassDX (2.5.0.0beta3)
* Merge KeePassDroid 2.2.0.9 * Merge KeePassDroid 2.2.0.9
* Add corruption fix mode * Add corruption fix mode
KeepassDX (2.5.0.0beta2) KeePassDX (2.5.0.0beta2)
* Remove libs for F-Droid * Remove libs for F-Droid
KeepassDX (2.5.0.0beta1) KeePassDX (2.5.0.0beta1)
* Fork KeepassDroid * Fork KeePassDroid
* Add Material Design * Add Material Design
* Add Light and Night theme * Add Light and Night theme
* Min API is 14 * Min API is 14
* Solve bug for fingerprint * Solve bug for fingerprint
* Update French translation * Update French translation
* Change donation (see KeepassDroid to contribute on both projects) * Change donation (see KeePassDroid to contribute on both projects)
KeePassDroid (2.2.1) KeePassDroid (2.2.1)
* Fix kdbx4 date corruption * Fix kdbx4 date corruption
@@ -446,7 +504,7 @@ KeePassDroid (1.9.10)
KeePassDroid (1.9.9) KeePassDroid (1.9.9)
* Go back to explicitly storing blank fields in the database * Go back to explicitly storing blank fields in the database
(works around bug in keepassx) (works around bug in KeePassx)
* Add support for native code on MIPS architectures * Add support for native code on MIPS architectures
* Adding Vibrate permission. On some devices notifications fail * Adding Vibrate permission. On some devices notifications fail
without the vibrate permission. without the vibrate permission.

View File

@@ -1,6 +1,6 @@
--- ---
KeePass DX is free software: you can redistribute it and/or modify KeePassDX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
@@ -13,7 +13,7 @@ KeePass DX is free software: you can redistribute it and/or modify
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
The KeePass DX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3. The KeePassDX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3.
--- ---

View File

@@ -1,6 +1,6 @@
# Android Keepass DX # Android KeepassDX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> Keepass DX is a **multi-format KeePass manager for Android devices**. The application allows to create keys and passwords in a secure way by integrating with the Android design standards. <img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeepassDX is a **multi-format KeePass manager for Android devices**. The application allows to create keys and passwords in a secure way by integrating with the Android design standards.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220"> <img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
@@ -8,28 +8,29 @@
* Create database files / entries and groups * Create database files / entries and groups
* Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm * Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePass XC...) * **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
* Allows **fast copy** of fields and opening of URI / URL * Allows **fast copy** of fields and opening of URI / URL
* **Fingerprint** for fast unlocking * **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
* **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
* Material design with **themes** * Material design with **themes**
* **AutoFill** and Integration * **AutoFill** and Integration
* Field filling **keyboard** * Field filling **keyboard**
* 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)*
Keepass DX is **open source** and **ad-free**. KeepassDX is **open source** and **ad-free**.
## What is KeePass DX? ## What is KeePassDX?
Today you need to remember many passwords. You need a password for your e-mail account, your website's FTP password, online passwords (like website member account), etc. etc. etc. The list is endless. Also, you **should use different passwords for each account**. Because if you use only one password everywhere and someone gets this password you have a problem... A serious problem. The thief would have access to your e-mail account, website, etc. Unimaginable. Today you need to remember many passwords. You need a password for your e-mail account, your website's FTP password, online passwords (like website member account), etc. etc. etc. The list is endless. Also, you **should use different passwords for each account**. Because if you use only one password everywhere and someone gets this password you have a problem... A serious problem. The thief would have access to your e-mail account, website, etc. Unimaginable.
KeePass DX is a **free open source password manager for Android**, which helps you to **manage your passwords in a secure way**. You can put all your passwords in one database, which is locked with one **master key** or a **key file**. So you **only have to remember one single master password or select the key file** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known. KeePassDX is a **free open source password manager for Android**, which helps you to **manage your passwords in a secure way**. You can put all your passwords in one database, which is locked with one **master key** or a **key file**. So you **only have to remember one single master password or select the key file** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
## Is it really free? ## Is it really free?
Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly. Yes, KeePassDX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.* *Note : If you access the application from a store, visual styles may not be available to encourage contribution to the work of open source projects. These optional styles are accessible after a contribution (and a congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
## Contributions ## Contributions
@@ -38,7 +39,7 @@ You can contribute in different ways to help us on our work.
* Add features by a **[pull request](https://help.github.com/articles/about-pull-requests/)**. * Add features by a **[pull request](https://help.github.com/articles/about-pull-requests/)**.
* Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** into your language (By using [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or with a manual [pull request](https://help.github.com/articles/about-pull-requests/)) * Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** into your language (By using [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or with a manual [pull request](https://help.github.com/articles/about-pull-requests/))
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features. * **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePass DX * Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePassDX
## Download ## Download
@@ -52,33 +53,35 @@ 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://www.keepassdx.com/FAQ) Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
## Other devices ## Other devices
- [KeePass XC](https://keepassxc.org/) (https://keepassxc.org/) works with **GNU/Linux**, **Mac** and **Windows**, is updated regularly and under the terms of the GNU General Public License. This is the recommended version for computers. - [KeePass](https://keepass.info/) (https://keepass.info/) is the original and official project for desktop, with technical documentation for standardized database files. It is updated regularly with active maintenance (written in C#).
- [KeePass](https://keepass.info/) (https://keepass.info/) is the historical project, with good technical documentation for standardized database files but only running on **Windows**. - [KeePassXC](https://keepassxc.org/) (https://keepassxc.org/) is an alternative integration to KeePass written in C++.
- [KeeWeb](https://keeweb.info/) (https://keeweb.info/) is a web version also compatible with KeePass files.
## License ## License
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com). Copyright (c) 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePass DX. This file is part of KeePassDX.
[KeePass DX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify [KeePassDX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
KeePass DX is distributed in the hope that it will be useful, KeePassDX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.* *This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -4,22 +4,28 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion '28.0.3' buildToolsVersion '29.0.3'
ndkVersion "20.0.5594570"
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 28 targetSdkVersion 29
versionCode = 24 versionCode = 31
versionName = "2.5.0.0beta24" versionName = "2.5RC1"
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\"}"
kapt {
arguments {
arg("room.incremental", "true")
arg("room.schemaLocation", "$projectDir/schemas".toString())
}
}
} }
externalNativeBuild { externalNativeBuild {
@@ -28,7 +34,6 @@ android {
} }
} }
buildTypes { buildTypes {
release { release {
minifyEnabled = false minifyEnabled = false
@@ -81,7 +86,7 @@ android {
} }
def spongycastleVersion = "1.58.0.0" def spongycastleVersion = "1.58.0.0"
def room_version = "2.2.0" 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"
@@ -90,7 +95,10 @@ dependencies {
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.biometric:biometric:1.0.0-rc01' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.0.1'
implementation "androidx.core:core-ktx:1.2.0"
// To upgrade with style
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
@@ -107,8 +115,8 @@ dependencies {
// 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'
// Base64 // Apache Commons Codec
implementation 'biz.source_code:base64coder:2010-12-19' implementation 'commons-codec:commons-codec:1.11'
// Icon pack // Icon pack
implementation project(path: ':icon-pack-classic') implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material') implementation project(path: ':icon-pack-material')

View File

@@ -0,0 +1,84 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "56438e5f7372ef3e36e33b782aed245d",
"entities": [
{
"tableName": "file_database_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `database_alias` TEXT NOT NULL, `keyfile_uri` TEXT, `updated` INTEGER NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "databaseAlias",
"columnName": "database_alias",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "keyFileUri",
"columnName": "keyfile_uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "updated",
"columnName": "updated",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cipher_database",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `encrypted_value` TEXT NOT NULL, `specs_parameters` TEXT NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptedValue",
"columnName": "encrypted_value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "specParameters",
"columnName": "specs_parameters",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '56438e5f7372ef3e36e33b782aed245d')"
]
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests
import junit.framework.TestCase
import com.kunzisoft.keepass.database.element.PwDate
import org.junit.Assert
class PwDateTest : TestCase() {
fun testDate() {
val jDate = PwDate(System.currentTimeMillis())
val intermediate = PwDate(jDate)
val cDate = PwDate(intermediate.byteArrayDate!!, 0)
Assert.assertTrue("jDate and intermediate not equal", jDate == intermediate)
Assert.assertTrue("jDate and cDate not equal", cDate == jDate)
}
}

View File

@@ -1,38 +1,33 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. 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
import org.junit.Assert.assertArrayEquals import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import java.io.ByteArrayOutputStream import com.kunzisoft.keepass.stream.*
import java.util.Calendar
import java.util.Random
import junit.framework.TestCase import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.*
import com.kunzisoft.keepass.database.element.PwDate class StringDatabaseKDBUtilsTest : TestCase() {
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.Types
class TypesTest : TestCase() {
fun testReadWriteLongZero() { fun testReadWriteLongZero() {
testReadWriteLong(0.toByte()) testReadWriteLong(0.toByte())
@@ -56,15 +51,9 @@ class TypesTest : TestCase() {
private fun testReadWriteLong(value: Byte) { private fun testReadWriteLong(value: Byte) {
val orig = ByteArray(8) val orig = ByteArray(8)
val dest = ByteArray(8) setArray(orig, value, 8)
setArray(orig, value, 0, 8)
val one = LEDataInputStream.readLong(orig, 0)
LEDataOutputStream.writeLong(one, dest, 0)
assertArrayEquals(orig, dest)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
} }
fun testReadWriteIntZero() { fun testReadWriteIntZero() {
@@ -81,24 +70,22 @@ class TypesTest : TestCase() {
private fun testReadWriteInt(value: Byte) { private fun testReadWriteInt(value: Byte) {
val orig = ByteArray(4) val orig = ByteArray(4)
val dest = ByteArray(4)
for (i in 0..3) { for (i in 0..3) {
orig[i] = 0 orig[i] = 0
} }
setArray(orig, value, 0, 4) setArray(orig, value, 4)
val one = LEDataInputStream.readInt(orig, 0) val one = bytes4ToInt(orig)
val dest = intTo4Bytes(one)
LEDataOutputStream.writeInt(one, dest, 0)
assertArrayEquals(orig, dest) assertArrayEquals(orig, dest)
} }
private fun setArray(buf: ByteArray, value: Byte, offset: Int, size: Int) { private fun setArray(buf: ByteArray, value: Byte, size: Int) {
for (i in offset until offset + size) { for (i in 0 until size) {
buf[i] = value buf[i] = value
} }
} }
@@ -109,11 +96,10 @@ class TypesTest : TestCase() {
orig[0] = 0 orig[0] = 0
orig[1] = 1 orig[1] = 1
val one = LEDataInputStream.readUShort(orig, 0) val one = bytes2ToUShort(orig)
val dest = LEDataOutputStream.writeUShortBuf(one) val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest) assertArrayEquals(orig, dest)
} }
fun testReadWriteShortMin() { fun testReadWriteShortMin() {
@@ -126,15 +112,12 @@ class TypesTest : TestCase() {
private fun testReadWriteShort(value: Byte) { private fun testReadWriteShort(value: Byte) {
val orig = ByteArray(2) val orig = ByteArray(2)
val dest = ByteArray(2) setArray(orig, value, 2)
setArray(orig, value, 0, 2) val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
val one = LEDataInputStream.readUShort(orig, 0)
LEDataOutputStream.writeUShort(one, dest, 0)
assertArrayEquals(orig, dest) assertArrayEquals(orig, dest)
} }
fun testReadWriteByteZero() { fun testReadWriteByteZero() {
@@ -150,16 +133,8 @@ class TypesTest : TestCase() {
} }
private fun testReadWriteByte(value: Byte) { private fun testReadWriteByte(value: Byte) {
val orig = ByteArray(1) val dest: Byte = uIntToByte(byteToUInt(value))
val dest = ByteArray(1) assert(value == dest)
setArray(orig, value, 0, 1)
val one = Types.readUByte(orig, 0)
Types.writeUByte(one, dest, 0)
assertArrayEquals(orig, dest)
} }
fun testDate() { fun testDate() {
@@ -168,27 +143,37 @@ class TypesTest : TestCase() {
val expected = Calendar.getInstance() val expected = Calendar.getInstance()
expected.set(2008, 1, 2, 3, 4, 5) expected.set(2008, 1, 2, 3, 4, 5)
val buf = PwDate.writeTime(expected.time, cal)
val actual = Calendar.getInstance() val actual = Calendar.getInstance()
actual.time = PwDate.readTime(buf, 0, cal) dateTo5Bytes(expected.time, cal)?.let { buf ->
actual.time = bytes5ToDate(buf, cal).date
}
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!)
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR)) assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH)) assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH)) assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY)) assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE)) assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND)) assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
assertTrue("jDate and intermediate not equal", jDate == intermediate)
assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
} }
fun testUUID() { fun testUUID() {
val rnd = Random()
val bUUID = ByteArray(16) val bUUID = ByteArray(16)
rnd.nextBytes(bUUID) Random().nextBytes(bUUID)
val uuid = Types.bytestoUUID(bUUID) val uuid = bytes16ToUuid(bUUID)
val eUUID = Types.UUIDtoBytes(uuid) val eUUID = uuidTo16Bytes(uuid)
val lUUID = bytes16ToUuid(bUUID)
val leUUID = uuidTo16Bytes(lUUID)
assertArrayEquals("UUID match failed", bUUID, eUUID) assertArrayEquals("UUID match failed", bUUID, eUUID)
assertArrayEquals("UUID match failed", bUUID, leUUID)
} }
@Throws(Exception::class) @Throws(Exception::class)
@@ -199,8 +184,8 @@ class TypesTest : TestCase() {
} }
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
val leos = LEDataOutputStream(bos) val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(Types.ULONG_MAX_VALUE) leos.writeLong(ULONG_MAX_VALUE)
leos.close() leos.close()
val uLongMax = bos.toByteArray() val uLongMax = bos.toByteArray()

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.tests.crypto package com.kunzisoft.keepass.tests.crypto

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.tests.crypto package com.kunzisoft.keepass.tests.crypto
@@ -39,9 +39,8 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.engine.AesEngine import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.stream.BetterCipherInputStream import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
class CipherTest : TestCase() { class CipherTest : TestCase() {
private val rand = Random() private val rand = Random()
@@ -93,7 +92,7 @@ class CipherTest : TestCase() {
val bis = ByteArrayInputStream(secrettext) val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt) val cis = BetterCipherInputStream(bis, decrypt)
val lis = LEDataInputStream(cis) val lis = LittleEndianDataInputStream(cis)
val decrypttext = lis.readBytes(MESSAGE_LENGTH) val decrypttext = lis.readBytes(MESSAGE_LENGTH)

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.tests.crypto package com.kunzisoft.keepass.tests.crypto

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.tests.stream package com.kunzisoft.keepass.tests.stream

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.tests.utils package com.kunzisoft.keepass.tests.utils

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass" package="com.kunzisoft.keepass"
android:installLocation="auto"> android:installLocation="auto">
<supports-screens <supports-screens
@@ -7,10 +8,14 @@
android:normalScreens="true" android:normalScreens="true"
android:largeScreens="true" android:largeScreens="true"
android:anyDensity="true" /> android:anyDensity="true" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission
<uses-permission android:name="android.permission.VIBRATE"/> android:name="android.permission.USE_BIOMETRIC" />
<uses-permission
android:name="android.permission.VIBRATE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application <application
android:label="@string/app_name" android:label="@string/app_name"
@@ -20,7 +25,10 @@
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup" android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:theme="@style/KeepassDXStyle.Night"> android:largeHeap="true"
android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night"
tools:targetApi="n">
<!-- TODO backup API Key --> <!-- TODO backup API Key -->
<meta-data <meta-data
android:name="com.google.android.backup.api_key" android:name="com.google.android.backup.api_key"
@@ -124,28 +132,35 @@
<activity <activity
android:name="com.kunzisoft.keepass.activities.AboutActivity" android:name="com.kunzisoft.keepass.activities.AboutActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:label="@string/menu_about" /> android:label="@string/about" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" /> <activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<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"/>
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name="com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService"
android:enabled="true"
android:exported="false" />
<service <service
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService" android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<service
android:name=".notifications.AttachmentFileNotificationService"
android:enabled="true"
android:exported="false" />
<service <service
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService" android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
android:enabled="true" android:enabled="true"

View File

@@ -1,35 +1,35 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.widget.Toolbar import android.text.method.LinkMovementMethod
import android.util.Log import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.core.text.HtmlCompat
import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
import org.joda.time.DateTime import org.joda.time.DateTime
class AboutActivity : StylishActivity() { class AboutActivity : StylishActivity() {
@@ -40,7 +40,7 @@ class AboutActivity : StylishActivity() {
setContentView(R.layout.activity_about) setContentView(R.layout.activity_about)
val toolbar = findViewById<Toolbar>(R.id.toolbar) val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = getString(R.string.menu_about) toolbar.title = getString(R.string.about)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
@@ -64,9 +64,17 @@ class AboutActivity : StylishActivity() {
val buildTextView = findViewById<TextView>(R.id.activity_about_build) val buildTextView = findViewById<TextView>(R.id.activity_about_build)
buildTextView.text = build buildTextView.text = build
findViewById<TextView>(R.id.activity_about_licence_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_licence, DateTime().year),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
val disclaimerText = findViewById<TextView>(R.id.disclaimer) findViewById<TextView>(R.id.activity_about_contribution_text).apply {
disclaimerText.text = getString(R.string.disclaimer_formal, DateTime().year) movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
@@ -22,51 +22,72 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import com.google.android.material.appbar.CollapsingToolbarLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
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.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingHideActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.PwNodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation import com.kunzisoft.keepass.education.EntryActivityEducation
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.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_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.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
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.utils.createDocument
import com.kunzisoft.keepass.utils.onCreateDocumentResult
import com.kunzisoft.keepass.view.EntryContentsView import com.kunzisoft.keepass.view.EntryContentsView
import com.kunzisoft.keepass.view.showActionError
import java.util.* import java.util.*
import kotlin.collections.HashMap
class EntryActivity : LockingHideActivity() { class EntryActivity : LockingActivity() {
private var coordinatorLayout: CoordinatorLayout? = null
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var titleIconView: ImageView? = null private var titleIconView: ImageView? = null
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 lockView: View? = null
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
private var mDatabase: Database? = null private var mDatabase: Database? = null
private var mEntry: EntryVersioned? = null private var mEntry: Entry? = null
private var mIsHistory: Boolean = false private var mIsHistory: Boolean = false
private var mEntryLastVersion: Entry? = null
private var mEntryHistoryPosition: Int = -1
private var mShowPassword: Boolean = false private var mShowPassword: Boolean = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
private var clipboardHelper: ClipboardHelper? = null private var clipboardHelper: ClipboardHelper? = null
private var firstLaunchOfActivity: Boolean = false private var firstLaunchOfActivity: Boolean = false
@@ -96,29 +117,62 @@ class EntryActivity : LockingHideActivity() {
invalidateOptionsMenu() invalidateOptionsMenu()
// Get views // Get views
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
collapsingToolbarLayout = findViewById(R.id.toolbar_layout) collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
titleIconView = findViewById(R.id.entry_icon) titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container) historyView = findViewById(R.id.history_container)
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)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
// Init the clipboard helper // Init the clipboard helper
clipboardHelper = ClipboardHelper(this) clipboardHelper = ClipboardHelper(this)
firstLaunchOfActivity = true firstLaunchOfActivity = true
// Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
mProgressDialogThread?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
// Close the current activity after an history action
if (result.isSuccess)
finish()
}
}
coordinatorLayout?.showActionError(result)
}
} }
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: PwNodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY) val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY)
mEntry = mDatabase?.getEntryById(keyEntry) if (keyEntry != null) {
mEntry = mDatabase?.getEntryById(keyEntry)
mEntryLastVersion = mEntry
}
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key") Log.e(TAG, "Unable to retrieve the entry key")
} }
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1) val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
mEntryHistoryPosition = historyPosition
if (historyPosition >= 0) { if (historyPosition >= 0) {
mIsHistory = true mIsHistory = true
mEntry = mEntry?.getHistory()?.get(historyPosition) mEntry = mEntry?.getHistory()?.get(historyPosition)
@@ -152,10 +206,25 @@ class EntryActivity : LockingHideActivity() {
} }
} }
mAttachmentFileBinderManager?.apply {
registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
entryContentsView?.updateAttachmentDownloadProgress(attachment)
}
}
}
firstLaunchOfActivity = false firstLaunchOfActivity = false
} }
private fun fillEntryDataInContentsView(entry: EntryVersioned) { override fun onPause() {
mAttachmentFileBinderManager?.unregisterProgressTask()
super.onPause()
}
private fun fillEntryDataInContentsView(entry: Entry) {
val database = Database.getInstance() val database = Database.getInstance()
database.startManageEntry(entry) database.startManageEntry(entry)
@@ -188,7 +257,7 @@ class EntryActivity : LockingHideActivity() {
"\n\n" + "\n\n" +
getString(R.string.clipboard_warning)) getString(R.string.clipboard_warning))
.create().apply { .create().apply {
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) {dialog, _ -> setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true) PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
dialog.dismiss() dialog.dismiss()
fillEntryDataInContentsView(entry) fillEntryDataInContentsView(entry)
@@ -220,6 +289,17 @@ class EntryActivity : LockingHideActivity() {
} }
} }
//Assign OTP field
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
View.OnClickListener {
entry.getOtpElement()?.let { otpElement ->
clipboardHelper?.timeoutCopyToClipboard(
otpElement.token,
getString(R.string.copy_field, getString(R.string.entry_otp))
)
}
})
entryContentsView?.assignURL(entry.url) entryContentsView?.assignURL(entry.url)
entryContentsView?.assignComment(entry.notes) entryContentsView?.assignComment(entry.notes)
@@ -251,6 +331,27 @@ class EntryActivity : LockingHideActivity() {
} }
entryContentsView?.setHiddenPasswordStyle(!mShowPassword) entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
// Manage attachments
val attachments = entry.getAttachments()
val showAttachmentsView = attachments.isNotEmpty()
entryContentsView?.showAttachments(showAttachmentsView)
if (showAttachmentsView) {
entryContentsView?.assignAttachments(attachments)
entryContentsView?.onAttachmentClick { attachmentItem, _ ->
when (attachmentItem.downloadState) {
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
createDocument(this, attachmentItem.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentItem
}
}
else -> {
// TODO Stop download
}
}
}
}
entryContentsView?.refreshAttachments()
// Assign dates // Assign dates
entryContentsView?.assignCreationDate(entry.creationTime) entryContentsView?.assignCreationDate(entry.creationTime)
entryContentsView?.assignModificationDate(entry.lastModificationTime) entryContentsView?.assignModificationDate(entry.lastModificationTime)
@@ -262,9 +363,6 @@ class EntryActivity : LockingHideActivity() {
entryContentsView?.assignExpiresDate(getString(R.string.never)) entryContentsView?.assignExpiresDate(getString(R.string.never))
} }
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
// Manage history // Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
if (mIsHistory) { if (mIsHistory) {
@@ -273,21 +371,26 @@ class EntryActivity : LockingHideActivity() {
taColorAccent.recycle() taColorAccent.recycle()
} }
val entryHistory = entry.getHistory() val entryHistory = entry.getHistory()
// isMainEntry = not an history // TODO isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty() val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView) entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) { if (showHistoryView) {
entryContentsView?.assignHistory(entryHistory) entryContentsView?.assignHistory(entryHistory)
entryContentsView?.onHistoryClick { historyItem, position -> entryContentsView?.onHistoryClick { historyItem, position ->
launch(this, historyItem, true, position) launch(this, historyItem, mReadOnly, position)
} }
} }
entryContentsView?.refreshHistory()
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
database.stopManageEntry(entry) database.stopManageEntry(entry)
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
// Not directly get the entry from intent data but from database // Not directly get the entry from intent data but from database
@@ -295,6 +398,15 @@ class EntryActivity : LockingHideActivity() {
fillEntryDataInContentsView(it) fillEntryDataInContentsView(it)
} }
} }
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
}
}
}
} }
private fun changeShowPasswordIcon(togglePassword: MenuItem?) { private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
@@ -313,9 +425,12 @@ class EntryActivity : LockingHideActivity() {
val inflater = menuInflater val inflater = menuInflater
MenuUtil.contributionMenuInflater(inflater, menu) MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu) inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database_lock, menu) inflater.inflate(R.menu.database, menu)
if (mIsHistory && !mReadOnly) {
if (mReadOnly) { inflater.inflate(R.menu.entry_history, menu)
}
if (mIsHistory || mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false menu.findItem(R.id.menu_edit)?.isVisible = false
} }
@@ -359,8 +474,7 @@ class EntryActivity : LockingHideActivity() {
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) {
@@ -384,21 +498,18 @@ class EntryActivity : LockingHideActivity() {
MenuUtil.onContributionItemSelected(this) MenuUtil.onContributionItemSelected(this)
return true return true
} }
R.id.menu_toggle_pass -> { R.id.menu_toggle_pass -> {
mShowPassword = !mShowPassword mShowPassword = !mShowPassword
changeShowPasswordIcon(item) changeShowPasswordIcon(item)
entryContentsView?.setHiddenPasswordStyle(!mShowPassword) entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
return true return true
} }
R.id.menu_edit -> { R.id.menu_edit -> {
mEntry?.let { mEntry?.let {
EntryEditActivity.launch(this@EntryActivity, it) EntryEditActivity.launch(this@EntryActivity, it)
} }
return true return true
} }
R.id.menu_goto_url -> { R.id.menu_goto_url -> {
var url: String = mEntry?.url ?: "" var url: String = mEntry?.url ?: ""
@@ -408,30 +519,39 @@ class EntryActivity : LockingHideActivity() {
} }
UriUtil.gotoUrl(this, url) UriUtil.gotoUrl(this, url)
return true return true
} }
R.id.menu_restore_entry_history -> {
R.id.menu_lock -> { mEntryLastVersion?.let { mainEntry ->
lockAndExit() mProgressDialogThread?.startDatabaseRestoreEntryHistory(
return true mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_delete_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseDeleteEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
} }
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any) android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun finish() { override fun finish() {
// Transit data in previous Activity after an update // Transit data in previous Activity after an update
/* Intent().apply {
TODO Slowdown when add entry as result putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry)
Intent intent = new Intent(); setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, this)
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry); }
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish() super.finish()
} }
@@ -441,7 +561,7 @@ class EntryActivity : LockingHideActivity() {
const val KEY_ENTRY = "KEY_ENTRY" const val KEY_ENTRY = "KEY_ENTRY"
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION" const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
fun launch(activity: Activity, entry: EntryVersioned, readOnly: Boolean, historyPosition: Int? = null) { fun launch(activity: Activity, entry: Entry, readOnly: Boolean, historyPosition: Int? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java) val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId) intent.putExtra(KEY_ENTRY, entry.nodeId)

View File

@@ -1,24 +1,26 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.app.Activity import android.app.Activity
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@@ -26,43 +28,60 @@ 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.ScrollView import android.widget.DatePicker
import android.widget.TimePicker
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.ActionMenuView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.widget.NestedScrollView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.LockingHideActivity import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.DateInstant
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.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView import com.kunzisoft.keepass.view.EntryEditContentsView
import com.kunzisoft.keepass.view.showActionError
import org.joda.time.DateTime
import java.util.* import java.util.*
class EntryEditActivity : LockingHideActivity(), class EntryEditActivity : LockingActivity(),
IconPickerDialogFragment.IconPickerListener, IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener { GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener,
DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener {
private var mDatabase: Database? = null private var mDatabase: Database? = null
// Refs of an entry and group in database, are not modifiable // Refs of an entry and group in database, are not modifiable
private var mEntry: EntryVersioned? = null private var mEntry: Entry? = null
private var mParent: GroupVersioned? = null private var mParent: Group? = null
// New or copy of mEntry in the database to be modifiable // New or copy of mEntry in the database to be modifiable
private var mNewEntry: EntryVersioned? = null private var mNewEntry: Entry? = null
private var mIsNew: Boolean = false private var mIsNew: Boolean = false
// Views // Views
private var scrollView: ScrollView? = null private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: NestedScrollView? = null
private var entryEditContentsView: EntryEditContentsView? = null private var entryEditContentsView: EntryEditContentsView? = null
private var entryEditAddToolBar: ActionMenuView? = null
private var saveView: View? = null private var saveView: View? = null
private var lockView: View? = null
// Dialog thread
private var progressDialogThread: ProgressDialogThread? = null
// Education // Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null private var entryEditActivityEducation: EntryEditActivityEducation? = null
@@ -77,19 +96,40 @@ class EntryEditActivity : LockingHideActivity(),
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
scrollView = findViewById(R.id.entry_edit_scroll) scrollView = findViewById(R.id.entry_edit_scroll)
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
entryEditContentsView = findViewById(R.id.entry_edit_contents) entryEditContentsView = findViewById(R.id.entry_edit_contents)
entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this)) entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryEditContentsView?.onDateClickListener = View.OnClickListener {
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
val dateTime = DateTime(expiresDate)
val defaultYear = dateTime.year
val defaultMonth = dateTime.monthOfYear-1
val defaultDay = dateTime.dayOfMonth
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
.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)
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
// Likely the app has been killed exit the activity // Likely the app has been killed exit the activity
mDatabase = Database.getInstance() mDatabase = Database.getInstance()
// Entry is retrieve, it's an entry to update // Entry is retrieve, it's an entry to update
intent.getParcelableExtra<PwNodeId<UUID>>(KEY_ENTRY)?.let { intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
mIsNew = false mIsNew = false
// Create an Entry copy to modify from the database entry // Create an Entry copy to modify from the database entry
mEntry = mDatabase?.getEntryById(it) mEntry = mDatabase?.getEntryById(it)
@@ -109,7 +149,7 @@ class EntryEditActivity : LockingHideActivity(),
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) { || !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
mEntry?.let { entry -> mEntry?.let { entry ->
// Create a copy to modify // Create a copy to modify
mNewEntry = EntryVersioned(entry).also { newEntry -> mNewEntry = Entry(entry).also { newEntry ->
// WARNING Remove the parent to keep memory with parcelable // WARNING Remove the parent to keep memory with parcelable
newEntry.removeParent() newEntry.removeParent()
} }
@@ -118,7 +158,7 @@ class EntryEditActivity : LockingHideActivity(),
} }
// Parent is retrieve, it's a new entry to create // Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let { intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
mIsNew = true mIsNew = true
// Create an empty new entry // Create an empty new entry
if (savedInstanceState == null if (savedInstanceState == null
@@ -152,20 +192,51 @@ class EntryEditActivity : LockingHideActivity(),
// Add listener to the icon // Add listener to the icon
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) } entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
// Generate password button // Bottom Bar
entryEditContentsView?.setOnPasswordGeneratorClickListener { openPasswordGenerator() } entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
entryEditAddToolBar?.apply {
menuInflater.inflate(R.menu.entry_edit, menu)
menu.findItem(R.id.menu_add_field).apply {
val allowCustomField = mNewEntry?.allowCustomFields() == true
isEnabled = allowCustomField
isVisible = allowCustomField
}
menu.findItem(R.id.menu_add_otp).apply {
val allowOTP = mDatabase?.allowOTP == true
isEnabled = allowOTP
isVisible = allowOTP
}
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.menu_generate_password -> {
openPasswordGenerator()
true
}
R.id.menu_add_field -> {
addNewCustomField()
true
}
R.id.menu_add_otp -> {
setupOTP()
true
}
else -> true
}
}
}
// Save button // Save button
saveView = findViewById(R.id.entry_edit_save) saveView = findViewById(R.id.entry_edit_validate)
saveView?.setOnClickListener { saveEntry() } saveView?.setOnClickListener { saveEntry() }
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) { addNewCustomField() }
// Verify the education views // Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this) entryEditActivityEducation = EntryEditActivityEducation(this)
// Create progress dialog // Create progress dialog
progressDialogThread = ProgressDialogThread(this) { actionTask, result -> mProgressDialogThread?.onActionFinish = { actionTask, result ->
when (actionTask) { when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK, ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> { ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
@@ -173,10 +244,21 @@ class EntryEditActivity : LockingHideActivity(),
finish() finish()
} }
} }
coordinatorLayout?.showActionError(result)
} }
} }
private fun populateViewsWithEntry(newEntry: EntryVersioned) { override fun onResume() {
super.onResume()
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
}
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)
@@ -189,16 +271,19 @@ class EntryEditActivity : LockingHideActivity(),
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
url = newEntry.url url = newEntry.url
password = newEntry.password password = newEntry.password
expires = newEntry.expires
if (expires)
expiresDate = newEntry.expiryTime
notes = newEntry.notes notes = newEntry.notes
for (entry in newEntry.customFields.entries) { for (entry in newEntry.customFields.entries) {
post { post {
addNewCustomField(entry.key, entry.value) putCustomField(entry.key, entry.value)
} }
} }
} }
} }
private fun populateEntryWithViews(newEntry: EntryVersioned) { private fun populateEntryWithViews(newEntry: Entry) {
mDatabase?.startManageEntry(newEntry) mDatabase?.startManageEntry(newEntry)
@@ -210,9 +295,13 @@ class EntryEditActivity : LockingHideActivity(),
username = entryView.username username = entryView.username
url = entryView.url url = entryView.url
password = entryView.password password = entryView.password
notes = entryView.notes expires = entryView.expires
if (entryView.expires) {
expiryTime = entryView.expiresDate
}
notes = entryView. notes
entryView.customFields.forEach { customField -> entryView.customFields.forEach { customField ->
addExtraField(customField.name, customField.protectedValue) putExtraField(customField.name, customField.protectedValue)
} }
} }
} }
@@ -220,7 +309,7 @@ class EntryEditActivity : LockingHideActivity(),
mDatabase?.stopManageEntry(newEntry) mDatabase?.stopManageEntry(newEntry)
} }
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) { private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
mNewEntry?.icon = icon mNewEntry?.icon = icon
mDatabase?.drawFactory?.let { iconDrawFactory -> mDatabase?.drawFactory?.let { iconDrawFactory ->
entryEditContentsView?.setIcon(iconDrawFactory, icon) entryEditContentsView?.setIcon(iconDrawFactory, icon)
@@ -238,7 +327,14 @@ class EntryEditActivity : LockingHideActivity(),
* Add a new customized field view and scroll to bottom * Add a new customized field view and scroll to bottom
*/ */
private fun addNewCustomField() { private fun addNewCustomField() {
entryEditContentsView?.addNewCustomField() entryEditContentsView?.addEmptyCustomField()
}
private fun setupOTP() {
// Retrieve the current otpElement if exists
// and open the dialog to set up the OTP
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
.show(supportFragmentManager, "addOTPDialog")
} }
/** /**
@@ -254,26 +350,26 @@ class EntryEditActivity : LockingHideActivity(),
// WARNING Add the parent previously deleted // WARNING Add the parent previously deleted
newEntry.parent = mEntry?.parent newEntry.parent = mEntry?.parent
// Build info // Build info
newEntry.lastAccessTime = PwDate() newEntry.lastAccessTime = DateInstant()
newEntry.lastModificationTime = PwDate() newEntry.lastModificationTime = DateInstant()
populateEntryWithViews(newEntry) populateEntryWithViews(newEntry)
// Open a progress dialog and save entry // Open a progress dialog and save entry
if (mIsNew) { if (mIsNew) {
mParent?.let { parent -> mParent?.let { parent ->
progressDialogThread?.startDatabaseCreateEntry( mProgressDialogThread?.startDatabaseCreateEntry(
newEntry, newEntry,
parent, parent,
!mReadOnly !mReadOnly && mAutoSaveEnable
) )
} }
} else { } else {
mEntry?.let { oldEntry -> mEntry?.let { oldEntry ->
progressDialogThread?.startDatabaseUpdateEntry( mProgressDialogThread?.startDatabaseUpdateEntry(
oldEntry, oldEntry,
newEntry, newEntry,
!mReadOnly !mReadOnly && mAutoSaveEnable
) )
} }
} }
@@ -281,23 +377,13 @@ class EntryEditActivity : LockingHideActivity(),
} }
} }
override fun onResume() {
super.onResume()
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
val inflater = menuInflater val inflater = menuInflater
inflater.inflate(R.menu.database_lock, menu) inflater.inflate(R.menu.database, menu)
// Save database not needed here
menu.findItem(R.id.menu_save_database)?.isVisible = false
MenuUtil.contributionMenuInflater(inflater, menu) MenuUtil.contributionMenuInflater(inflater, menu)
entryEditActivityEducation?.let { entryEditActivityEducation?.let {
@@ -308,12 +394,10 @@ class EntryEditActivity : LockingHideActivity(),
} }
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) { private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
val passwordView = entryEditContentsView?.generatePasswordView val passwordGeneratorView: View? = entryEditAddToolBar?.findViewById(R.id.menu_generate_password)
val addNewFieldView = entryEditContentsView?.addNewFieldView val generatePasswordEducationPerformed = passwordGeneratorView != null
val generatePasswordEducationPerformed = passwordView != null
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation( && entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
passwordView, passwordGeneratorView,
{ {
openPasswordGenerator() openPasswordGenerator()
}, },
@@ -322,41 +406,95 @@ class EntryEditActivity : LockingHideActivity(),
} }
) )
if (!generatePasswordEducationPerformed) { if (!generatePasswordEducationPerformed) {
// entryNewFieldEducationPerformed val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
mNewEntry != null && mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty() val addNewFieldEducationPerformed = mNewEntry != null
&& mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE && addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation( && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView, addNewFieldView,
{ {
addNewCustomField() addNewCustomField()
}) },
{
performedNextEducation(entryEditActivityEducation)
}
)
if (!addNewFieldEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
setupOtpView != null && setupOtpView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
setupOtpView,
{
setupOTP()
})
}
} }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_lock -> { R.id.menu_save_database -> {
lockAndExit() mProgressDialogThread?.startDatabaseSave(!mReadOnly)
return true
} }
R.id.menu_contribute -> { R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this) MenuUtil.onContributionItemSelected(this)
return true return true
} }
android.R.id.home -> {
android.R.id.home -> finish() onBackPressed()
}
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onOtpCreated(otpElement: OtpElement) {
// Update the otp field with otpauth:// url
val otpField = OtpEntryFields.buildOtpField(otpElement,
mEntry?.title, mEntry?.username)
entryEditContentsView?.putCustomField(otpField.name, otpField.protectedValue)
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
}
override fun iconPicked(bundle: Bundle) { override fun iconPicked(bundle: Bundle) {
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon -> IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
temporarilySaveAndShowSelectedIcon(icon) temporarilySaveAndShowSelectedIcon(icon)
} }
} }
override fun onDateSet(datePicker: DatePicker?, year: Int, month: Int, day: Int) {
// To fix android 4.4 issue
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
if (datePicker?.isShown == true) {
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
// Save the date
entryEditContentsView?.expiresDate =
DateInstant(DateTime(expiresDate)
.withYear(year)
.withMonthOfYear(month + 1)
.withDayOfMonth(day)
.toDate())
// Launch the time picker
val dateTime = DateTime(expiresDate)
val defaultHour = dateTime.hourOfDay
val defaultMinute = dateTime.minuteOfHour
TimePickerFragment.getInstance(defaultHour, defaultMinute)
.show(supportFragmentManager, "TimePickerFragment")
}
}
}
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
// Save the date
entryEditContentsView?.expiresDate =
DateInstant(DateTime(expiresDate)
.withHourOfDay(hours)
.withMinuteOfHour(minutes)
.toDate())
}
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
mNewEntry?.let { mNewEntry?.let {
populateEntryWithViews(it) populateEntryWithViews(it)
@@ -380,6 +518,15 @@ class EntryEditActivity : LockingHideActivity(),
// Do nothing here // Do nothing here
} }
override fun onBackPressed() {
AlertDialog.Builder(this)
.setMessage(R.string.discard_changes)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.discard) { _, _ ->
super@EntryEditActivity.onBackPressed()
}.create().show()
}
override fun finish() { override fun finish() {
// Assign entry callback as a result in all case // Assign entry callback as a result in all case
try { try {
@@ -424,7 +571,7 @@ class EntryEditActivity : LockingHideActivity(),
* @param activity from activity * @param activity from activity
* @param pwEntry Entry to update * @param pwEntry Entry to update
*/ */
fun launch(activity: Activity, pwEntry: EntryVersioned) { fun launch(activity: Activity, pwEntry: Entry) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java) val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, pwEntry.nodeId) intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
@@ -438,7 +585,7 @@ class EntryEditActivity : LockingHideActivity(),
* @param activity from activity * @param activity from activity
* @param pwGroup Group who will contains new entry * @param pwGroup Group who will contains new entry
*/ */
fun launch(activity: Activity, pwGroup: GroupVersioned) { fun launch(activity: Activity, pwGroup: Group) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java) val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, pwGroup.nodeId) intent.putExtra(KEY_PARENT, pwGroup.nodeId)

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
@@ -26,35 +26,34 @@ 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.preference.PreferenceManager
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.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
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.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity 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.utils.MenuUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.* import kotlinx.android.synthetic.main.activity_file_selection.*
import java.io.FileNotFoundException import java.io.FileNotFoundException
@@ -63,7 +62,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
AssignMasterKeyDialogFragment.AssignPasswordDialogListener { AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
// Views // Views
private var fileListContainer: View? = null private var coordinatorLayout: CoordinatorLayout? = 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
@@ -76,7 +76,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
private var mOpenFileHelper: OpenFileHelper? = null private var mOpenFileHelper: OpenFileHelper? = null
private var progressDialogThread: ProgressDialogThread? = null private var mProgressDialogThread: ProgressDialogThread? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -84,31 +84,38 @@ class FileDatabaseSelectActivity : StylishActivity(),
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext) mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection) setContentView(R.layout.activity_file_selection)
fileListContainer = findViewById(R.id.container_file_list) coordinatorLayout = findViewById(R.id.activity_file_selection_coordinator_layout)
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 (Intent(Intent.ACTION_CREATE_DOCUMENT).apply { if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}.resolveActivity(packageManager) == null) {
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
else{
// There is an activity which can handle this intent. // There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE createButtonView?.visibility = View.VISIBLE
} }
else{
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
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)
@@ -123,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
@@ -132,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
@@ -146,8 +151,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
if (!(savedInstanceState != null if (!(savedInstanceState != null
&& savedInstanceState.containsKey(EXTRA_STAY) && savedInstanceState.containsKey(EXTRA_STAY)
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) { && savedInstanceState.getBoolean(EXTRA_STAY, false))) {
val prefs = PreferenceManager.getDefaultSharedPreferences(this) val databasePath = PreferencesUtil.getDefaultDatabasePath(this)
val databasePath = prefs.getString(PasswordActivity.KEY_DEFAULT_DATABASE_PATH, "")
UriUtil.parse(databasePath)?.let { databaseFileUri -> UriUtil.parse(databasePath)?.let { databaseFileUri ->
launchPasswordActivityWithPath(databaseFileUri) launchPasswordActivityWithPath(databaseFileUri)
@@ -163,13 +167,12 @@ class FileDatabaseSelectActivity : StylishActivity(),
} }
// Attach the dialog thread to this activity // Attach the dialog thread to this activity
progressDialogThread = ProgressDialogThread(this) { actionTask, result -> mProgressDialogThread = ProgressDialogThread(this).apply {
when (actionTask) { onActionFinish = { actionTask, _ ->
ACTION_DATABASE_CREATE_TASK -> { when (actionTask) {
// TODO Check ACTION_DATABASE_CREATE_TASK -> {
// mAdapterDatabaseHistory?.notifyDataSetChanged() GroupActivity.launch(this@FileDatabaseSelectActivity)
// updateFileListVisibility() }
GroupActivity.launch(this)
} }
} }
} }
@@ -180,23 +183,15 @@ class FileDatabaseSelectActivity : StylishActivity(),
*/ */
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
private fun createNewFile() { private fun createNewFile() {
try { createDocument(this, getString(R.string.database_file_name_default) +
startActivityForResult(Intent( getString(R.string.database_file_extension_default), "application/x-keepass")
Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default))
},
CREATE_FILE_REQUEST_CODE)
} catch (e: Exception) {
BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
}
} }
private fun fileNoFoundAction(e: FileNotFoundException) { private fun fileNoFoundAction(e: FileNotFoundException) {
val error = getString(R.string.file_not_found_content) val error = getString(R.string.file_not_found_content)
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show() coordinatorLayout?.let {
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
}
Log.e(TAG, error, e) Log.e(TAG, error, e)
} }
@@ -224,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)
} }
@@ -236,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)
} }
}) })
} }
@@ -257,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) {
@@ -284,24 +266,35 @@ class FileDatabaseSelectActivity : StylishActivity(),
super.onResume() super.onResume()
updateExternalStorageWarning()
// Construct adapter with listeners // Construct adapter with listeners
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList -> if (PreferencesUtil.showRecentFiles(this)) {
databaseFileHistoryList?.let { mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it) databaseFileHistoryList?.let { historyList ->
updateFileListVisibility() val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity)
mAdapterDatabaseHistory?.notifyDataSetChanged() mAdapterDatabaseHistory?.addDatabaseFileHistoryList(
// Show only uri accessible
historyList.filter {
if (hideBrokenLocations) {
FileDatabaseInfo(this@FileDatabaseSelectActivity,
it.databaseUri).exists
} else
true
})
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
} }
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
} }
// Register progress task // Register progress task
progressDialogThread?.registerProgressTask() mProgressDialogThread?.registerProgressTask()
} }
override fun onPause() { override fun onPause() {
// Unregister progress task // Unregister progress task
progressDialogThread?.unregisterProgressTask() mProgressDialogThread?.unregisterProgressTask()
super.onPause() super.onPause()
} }
@@ -314,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?) {
@@ -329,7 +315,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
mDatabaseFileUri?.let { databaseUri -> mDatabaseFileUri?.let { databaseUri ->
// Create the new database // Create the new database
progressDialogThread?.startDatabaseCreate( mProgressDialogThread?.startDatabaseCreate(
databaseUri, databaseUri,
masterPasswordChecked, masterPasswordChecked,
masterPassword, masterPassword,
@@ -365,15 +351,18 @@ class FileDatabaseSelectActivity : StylishActivity(),
} }
// Retrieve the created URI from the file manager // Retrieve the created URI from the file manager
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = data?.data mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) { if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true) AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog") .show(supportFragmentManager, "passwordDialog")
} else {
val error = getString(R.string.error_create_database)
coordinatorLayout?.let {
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
}
Log.e(TAG, error)
} }
// else {
// TODO Show error
// }
} }
} }
@@ -425,8 +414,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
private const val EXTRA_STAY = "EXTRA_STAY" private const val EXTRA_STAY = "EXTRA_STAY"
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI" private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
private const val CREATE_FILE_REQUEST_CODE = 3853
/* /*
* ------------------------- * -------------------------
* No Standard Launch, pass by PasswordActivity * No Standard Launch, pass by PasswordActivity
@@ -450,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

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
@@ -42,21 +42,27 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
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.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.ReadOnlyDialog
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.SortNodeEnum import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
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
@@ -71,17 +77,20 @@ import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.AddNodeButtonView import com.kunzisoft.keepass.view.AddNodeButtonView
import com.kunzisoft.keepass.view.ToolbarAction import com.kunzisoft.keepass.view.ToolbarAction
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionError
class GroupActivity : LockingActivity(), class GroupActivity : LockingActivity(),
GroupEditDialogFragment.EditGroupListener, GroupEditDialogFragment.EditGroupListener,
IconPickerDialogFragment.IconPickerListener, IconPickerDialogFragment.IconPickerListener,
ListNodesFragment.NodeClickListener, ListNodesFragment.NodeClickListener,
ListNodesFragment.NodesActionMenuListener, ListNodesFragment.NodesActionMenuListener,
DeleteNodesDialogFragment.DeleteNodeListener,
ListNodesFragment.OnScrollListener, ListNodesFragment.OnScrollListener,
SortDialogFragment.SortSelectionListener { SortDialogFragment.SortSelectionListener {
// 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
@@ -95,15 +104,12 @@ class GroupActivity : LockingActivity(),
private var mListNodesFragment: ListNodesFragment? = null private var mListNodesFragment: ListNodesFragment? = null
private var mCurrentGroupIsASearch: Boolean = false private var mCurrentGroupIsASearch: Boolean = false
private var mRequestStartupSearch = true
private var progressDialogThread: ProgressDialogThread? = null
// Nodes // Nodes
private var mRootGroup: GroupVersioned? = null private var mRootGroup: Group? = null
private var mCurrentGroup: GroupVersioned? = null private var mCurrentGroup: Group? = null
private var mOldGroupToUpdate: GroupVersioned? = null private var mOldGroupToUpdate: Group? = null
// TODO private var mNodeToCopy: NodeVersioned? = null
// TODO private var mNodeToMove: NodeVersioned? = null
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
@@ -130,23 +136,22 @@ 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)
/*
toolbarAction?.setNavigationOnClickListener {
toolbarAction?.collapse()
mNodeToCopy = null
mNodeToMove = null
}
*/
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView) resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
// Retrieve elements after an orientation change // Retrieve elements after an orientation change
if (savedInstanceState != null) { if (savedInstanceState != null) {
if (savedInstanceState.containsKey(REQUEST_STARTUP_SEARCH_KEY))
mRequestStartupSearch = savedInstanceState.getBoolean(REQUEST_STARTUP_SEARCH_KEY)
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY)) if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY) mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY)
} }
@@ -207,13 +212,13 @@ class GroupActivity : LockingActivity(),
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database) mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database)
// Init dialog thread // Init dialog thread
progressDialogThread = ProgressDialogThread(this) { actionTask, result -> mProgressDialogThread?.onActionFinish = { actionTask, result ->
var oldNodes: List<NodeVersioned> = ArrayList() var oldNodes: List<Node> = ArrayList()
result.data?.getBundle(OLD_NODES_KEY)?.let { oldNodesBundle -> result.data?.getBundle(OLD_NODES_KEY)?.let { oldNodesBundle ->
oldNodes = getListNodesFromBundle(database, oldNodesBundle) oldNodes = getListNodesFromBundle(database, oldNodesBundle)
} }
var newNodes: List<NodeVersioned> = ArrayList() var newNodes: List<Node> = ArrayList()
result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle -> result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle ->
newNodes = getListNodesFromBundle(database, newNodesBundle) newNodes = getListNodesFromBundle(database, newNodesBundle)
} }
@@ -234,39 +239,34 @@ class GroupActivity : LockingActivity(),
ACTION_DATABASE_DELETE_NODES_TASK -> { ACTION_DATABASE_DELETE_NODES_TASK -> {
if (result.isSuccess) { if (result.isSuccess) {
// Rebuild all the list the avoid bug when delete node from db sort // Rebuild all the list to avoid bug when delete node from sort
if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB) { mListNodesFragment?.rebuildList()
mListNodesFragment?.rebuildList()
} else {
// Use the old Nodes / entries unchanged with the old parent
mListNodesFragment?.removeNodes(oldNodes)
}
// Add trash in views list if it doesn't exists // Add trash in views list if it doesn't exists
if (database.isRecycleBinEnabled) { if (database.isRecycleBinEnabled) {
val recycleBin = database.recycleBin val recycleBin = database.recycleBin
if (mCurrentGroup != null && recycleBin != null val currentGroup = mCurrentGroup
&& mCurrentGroup!!.parent == null if (currentGroup != null && recycleBin != null
&& mCurrentGroup != recycleBin) { && currentGroup != recycleBin) {
if (mListNodesFragment?.contains(recycleBin) == true) // Recycle bin already here, simply update it
if (mListNodesFragment?.contains(recycleBin) == true) {
mListNodesFragment?.updateNode(recycleBin) mListNodesFragment?.updateNode(recycleBin)
else }
// Recycle bin not here, verify if parents are similar to add it
else if (currentGroup == recycleBin.parent) {
mListNodesFragment?.addNode(recycleBin) mListNodesFragment?.addNode(recycleBin)
}
} }
} }
} }
} }
} }
if (!result.isSuccess) { coordinatorLayout?.showActionError(result)
result.exception?.errorId?.let { errorId ->
coordinatorLayout?.let { coordinatorLayout ->
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
}
}
}
finishNodeAction() finishNodeAction()
refreshNumberOfChildren()
} }
} }
@@ -289,7 +289,7 @@ class GroupActivity : LockingActivity(),
} }
} }
private fun openSearchGroup(group: GroupVersioned?) { private fun openSearchGroup(group: Group?) {
// Delete the previous search fragment // Delete the previous search fragment
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG) val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
if (searchFragment != null) { if (searchFragment != null) {
@@ -301,11 +301,11 @@ class GroupActivity : LockingActivity(),
openGroup(group, true) openGroup(group, true)
} }
private fun openChildGroup(group: GroupVersioned) { private fun openChildGroup(group: Group) {
openGroup(group, false) openGroup(group, false)
} }
private fun openGroup(group: GroupVersioned?, isASearch: Boolean) { private fun openGroup(group: Group?, isASearch: Boolean) {
// Check TimeoutHelper // Check TimeoutHelper
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) { TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
// Open a group in a new fragment // Open a group in a new fragment
@@ -342,21 +342,23 @@ class GroupActivity : LockingActivity(),
mOldGroupToUpdate?.let { mOldGroupToUpdate?.let {
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, it) outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, it)
} }
outState.putBoolean(REQUEST_STARTUP_SEARCH_KEY, mRequestStartupSearch)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): GroupVersioned? { private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): Group? {
// Force read only if the database is like that // Force read only if the database is like that
mReadOnly = mDatabase?.isReadOnly == true || mReadOnly mReadOnly = mDatabase?.isReadOnly == true || mReadOnly
// If it's a search // If it's a search
if (Intent.ACTION_SEARCH == intent.action) { if (Intent.ACTION_SEARCH == intent.action) {
return mDatabase?.search(intent.getStringExtra(SearchManager.QUERY).trim { it <= ' ' }) val searchString = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
return mDatabase?.createVirtualGroupFromSearch(searchString)
} }
// else a real group // else a real group
else { else {
var pwGroupId: PwNodeId<*>? = null var pwGroupId: NodeId<*>? = null
if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) { if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY) pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY)
} else { } else {
@@ -365,7 +367,7 @@ class GroupActivity : LockingActivity(),
} }
Log.w(TAG, "Creating tree view") Log.w(TAG, "Creating tree view")
val currentGroup: GroupVersioned? val currentGroup: Group?
currentGroup = if (pwGroupId == null) { currentGroup = if (pwGroupId == null) {
mRootGroup mRootGroup
} else { } else {
@@ -422,14 +424,7 @@ class GroupActivity : LockingActivity(),
} }
// Assign number of children // Assign number of children
numberChildrenView?.apply { refreshNumberOfChildren()
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
// Show selection mode message if needed // Show selection mode message if needed
if (mSelectionMode) { if (mSelectionMode) {
@@ -445,18 +440,24 @@ class GroupActivity : LockingActivity(),
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
mCurrentGroup?.let { mCurrentGroup?.let {
val isRoot = it == mRootGroup
if (!it.allowAddEntryIfIsRoot()) if (!it.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled addEntryEnabled = it != mRootGroup && addEntryEnabled
if (isRoot) {
showWarnings()
}
} }
enableAddGroup(addGroupEnabled) enableAddGroup(addGroupEnabled)
enableAddEntry(addEntryEnabled) enableAddEntry(addEntryEnabled)
if (isEnable) showButton()
showButton() }
}
private fun refreshNumberOfChildren() {
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getNumberOfChildEntries(*Group.ChildFilter.getDefaults(context))?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
} }
} }
@@ -464,16 +465,16 @@ class GroupActivity : LockingActivity(),
addNodeButtonView?.hideButtonOnScrollListener(dy) addNodeButtonView?.hideButtonOnScrollListener(dy)
} }
override fun onNodeClick(node: NodeVersioned) { override fun onNodeClick(node: Node) {
when (node.type) { when (node.type) {
Type.GROUP -> try { Type.GROUP -> try {
openChildGroup(node as GroupVersioned) openChildGroup(node as Group)
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
Log.e(TAG, "Node can't be cast in Group") Log.e(TAG, "Node can't be cast in Group")
} }
Type.ENTRY -> try { Type.ENTRY -> try {
val entryVersioned = node as EntryVersioned val entryVersioned = node as Entry
EntrySelectionHelper.doEntrySelectionAction(intent, EntrySelectionHelper.doEntrySelectionAction(intent,
{ {
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly) EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
@@ -491,8 +492,10 @@ 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) {
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity, mDatabase?.let { database ->
entryVersioned.getEntryInfo(mDatabase!!)) AutofillHelper.buildResponse(this@GroupActivity,
entryVersioned.getEntryInfo(database))
}
} }
finish() finish()
}) })
@@ -507,9 +510,10 @@ class GroupActivity : LockingActivity(),
private fun finishNodeAction() { private fun finishNodeAction() {
actionNodeMode?.finish() actionNodeMode?.finish()
actionNodeMode = null actionNodeMode = null
addNodeButtonView?.showButton()
} }
override fun onNodeSelected(nodes: List<NodeVersioned>): Boolean { override fun onNodeSelected(nodes: List<Node>): Boolean {
if (nodes.isNotEmpty()) { if (nodes.isNotEmpty()) {
if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) { if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) {
mListNodesFragment?.actionNodesCallback(nodes, this)?.let { mListNodesFragment?.actionNodesCallback(nodes, this)?.let {
@@ -518,40 +522,41 @@ class GroupActivity : LockingActivity(),
} else { } else {
actionNodeMode?.invalidate() actionNodeMode?.invalidate()
} }
addNodeButtonView?.hideButton()
} else { } else {
finishNodeAction() finishNodeAction()
} }
return true return true
} }
override fun onOpenMenuClick(node: NodeVersioned): Boolean { override fun onOpenMenuClick(node: Node): Boolean {
finishNodeAction() finishNodeAction()
onNodeClick(node) onNodeClick(node)
return true return true
} }
override fun onEditMenuClick(node: NodeVersioned): Boolean { override fun onEditMenuClick(node: Node): Boolean {
finishNodeAction() finishNodeAction()
when (node.type) { when (node.type) {
Type.GROUP -> { Type.GROUP -> {
mOldGroupToUpdate = node as GroupVersioned mOldGroupToUpdate = node as Group
GroupEditDialogFragment.build(mOldGroupToUpdate!!) GroupEditDialogFragment.build(mOldGroupToUpdate!!)
.show(supportFragmentManager, .show(supportFragmentManager,
GroupEditDialogFragment.TAG_CREATE_GROUP) GroupEditDialogFragment.TAG_CREATE_GROUP)
} }
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as EntryVersioned) Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as Entry)
} }
return true return true
} }
override fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean { override fun onCopyMenuClick(nodes: List<Node>): Boolean {
actionNodeMode?.invalidate() actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally // Nothing here fragment calls onPasteMenuClick internally
return true return true
} }
override fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean { override fun onMoveMenuClick(nodes: List<Node>): Boolean {
actionNodeMode?.invalidate() actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally // Nothing here fragment calls onPasteMenuClick internally
@@ -559,56 +564,95 @@ class GroupActivity : LockingActivity(),
} }
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?, override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
nodes: List<NodeVersioned>): Boolean { nodes: List<Node>): Boolean {
when (pasteMode) { // Move or copy only if allowed (in root if allowed)
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> { if (mCurrentGroup != mDatabase?.rootGroup
// Copy || mDatabase?.rootCanContainsEntry() == true) {
mCurrentGroup?.let { newParent ->
progressDialogThread?.startDatabaseCopyNodes( when (pasteMode) {
nodes, ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
newParent, // Copy
!mReadOnly mCurrentGroup?.let { newParent ->
) mProgressDialogThread?.startDatabaseCopyNodes(
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
mCurrentGroup?.let { newParent ->
mProgressDialogThread?.startDatabaseMoveNodes(
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
else -> {
} }
} }
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> { } else {
// Move coordinatorLayout?.let { coordinatorLayout ->
mCurrentGroup?.let { newParent -> Snackbar.make(coordinatorLayout,
progressDialogThread?.startDatabaseMoveNodes( R.string.error_copy_entry_here,
nodes, Snackbar.LENGTH_LONG).asError().show()
newParent,
!mReadOnly
)
}
} }
else -> {}
} }
finishNodeAction() finishNodeAction()
return true return true
} }
override fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean { override fun onDeleteMenuClick(nodes: List<Node>): Boolean {
progressDialogThread?.startDatabaseDeleteNodes( val database = mDatabase
nodes,
!mReadOnly // If recycle bin enabled, ensure it exists
) if (database != null && database.isRecycleBinEnabled) {
database.ensureRecycleBinExists(resources)
}
// If recycle bin enabled and not in recycle bin, move in recycle bin
if (database != null
&& database.isRecycleBinEnabled
&& database.recycleBin != mCurrentGroup) {
mProgressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
)
}
// else open the dialog to confirm deletion
else {
DeleteNodesDialogFragment.getInstance(nodes)
.show(supportFragmentManager, "deleteNodesDialogFragment")
}
finishNodeAction() finishNodeAction()
return true return true
} }
override fun permanentlyDeleteNodes(nodes: List<Node>) {
mProgressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
)
}
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
mSearchSuggestionAdapter?.reInit(this) mSearchSuggestionAdapter?.reInit(this)
progressDialogThread?.registerProgressTask()
} }
override fun onPause() { override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause() super.onPause()
finishNodeAction() finishNodeAction()
@@ -618,20 +662,32 @@ class GroupActivity : LockingActivity(),
val inflater = menuInflater val inflater = menuInflater
inflater.inflate(R.menu.search, menu) inflater.inflate(R.menu.search, menu)
inflater.inflate(R.menu.database_lock, menu) inflater.inflate(R.menu.database, menu)
if (mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
}
if (!mSelectionMode) { if (!mSelectionMode) {
inflater.inflate(R.menu.default_menu, menu) inflater.inflate(R.menu.default_menu, menu)
MenuUtil.contributionMenuInflater(inflater, menu) MenuUtil.contributionMenuInflater(inflater, menu)
} }
// Menu for recycle bin
if (!mReadOnly
&& mDatabase?.isRecycleBinEnabled == true
&& mDatabase?.recycleBin == mCurrentGroup) {
inflater.inflate(R.menu.recycle_bin, menu)
}
// Get the SearchView and set the searchable configuration // Get the SearchView and set the searchable configuration
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
menu.findItem(R.id.menu_search)?.let { menu.findItem(R.id.menu_search)?.let {
val searchView = it.actionView as SearchView? val searchView = it.actionView as SearchView?
searchView?.apply { searchView?.apply {
setSearchableInfo(searchManager.getSearchableInfo( (searchManager?.getSearchableInfo(
ComponentName(this@GroupActivity, GroupActivity::class.java))) ComponentName(this@GroupActivity, GroupActivity::class.java)))?.let { searchableInfo ->
setSearchableInfo(searchableInfo)
}
setIconifiedByDefault(false) // Do not iconify the widget; expand it by default setIconifiedByDefault(false) // Do not iconify the widget; expand it by default
suggestionsAdapter = mSearchSuggestionAdapter suggestionsAdapter = mSearchSuggestionAdapter
setOnSuggestionListener(object : SearchView.OnSuggestionListener { setOnSuggestionListener(object : SearchView.OnSuggestionListener {
@@ -649,6 +705,13 @@ class GroupActivity : LockingActivity(),
} }
}) })
} }
// Expand the search view if defined in settings
if (mRequestStartupSearch
&& PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
// To request search only one time
mRequestStartupSearch = false
it.expandActionView()
}
} }
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
@@ -704,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)
@@ -728,8 +790,15 @@ class GroupActivity : LockingActivity(),
R.id.menu_search -> R.id.menu_search ->
//onSearchRequested(); //onSearchRequested();
return true return true
R.id.menu_lock -> { R.id.menu_save_database -> {
lockAndExit() mProgressDialogThread?.startDatabaseSave(!mReadOnly)
return true
}
R.id.menu_empty_recycle_bin -> {
mCurrentGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements
onDeleteMenuClick(listChildren)
}
return true return true
} }
else -> { else -> {
@@ -742,7 +811,7 @@ class GroupActivity : LockingActivity(),
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?, override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?, name: String?,
icon: PwIcon?) { icon: IconImage?) {
if (name != null && name.isNotEmpty() && icon != null) { if (name != null && name.isNotEmpty() && icon != null) {
when (action) { when (action) {
@@ -756,28 +825,33 @@ class GroupActivity : LockingActivity(),
// Not really needed here because added in runnable but safe // Not really needed here because added in runnable but safe
newGroup.parent = currentGroup newGroup.parent = currentGroup
progressDialogThread?.startDatabaseCreateGroup( mProgressDialogThread?.startDatabaseCreateGroup(
newGroup, currentGroup, !mReadOnly) newGroup,
currentGroup,
!mReadOnly && mAutoSaveEnable
)
} }
} }
} }
GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> { GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> {
// If update add new elements // If update add new elements
mOldGroupToUpdate?.let { oldGroupToUpdate -> mOldGroupToUpdate?.let { oldGroupToUpdate ->
GroupVersioned(oldGroupToUpdate).let { updateGroup -> val updateGroup = Group(oldGroupToUpdate).let { updateGroup ->
updateGroup.apply { updateGroup.apply {
// WARNING remove parent and children to keep memory // WARNING remove parent and children to keep memory
removeParent() removeParent()
removeChildren() // TODO concurrent exception removeChildren()
title = name title = name
this.icon = icon // TODO custom icon this.icon = icon // TODO custom icon
} }
// If group updated save it in the database
progressDialogThread?.startDatabaseUpdateGroup(
oldGroupToUpdate, updateGroup, !mReadOnly)
} }
// If group updated save it in the database
mProgressDialogThread?.startDatabaseUpdateGroup(
oldGroupToUpdate,
updateGroup,
!mReadOnly && mAutoSaveEnable
)
} }
} }
else -> {} else -> {}
@@ -787,7 +861,7 @@ class GroupActivity : LockingActivity(),
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?, override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?, name: String?,
icon: PwIcon?) { icon: IconImage?) {
// Do nothing here // Do nothing here
} }
@@ -798,16 +872,8 @@ class GroupActivity : LockingActivity(),
.iconPicked(bundle) .iconPicked(bundle)
} }
private fun showWarnings() { override fun onSortSelected(sortNodeEnum: SortNodeEnum, sortNodeParameters: SortNodeEnum.SortNodeParameters) {
if (Database.getInstance().isReadOnly) { mListNodesFragment?.onSortSelected(sortNodeEnum, sortNodeParameters)
if (PreferencesUtil.showReadOnlyWarning(this)) {
ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
}
}
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom)
} }
override fun startActivity(intent: Intent) { override fun startActivity(intent: Intent) {
@@ -851,8 +917,8 @@ class GroupActivity : LockingActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
} }
// Not directly get the entry from intent data but from database // Directly used the onActivityResult in fragment
mListNodesFragment?.rebuildList() mListNodesFragment?.onActivityResult(requestCode, resultCode, data)
} }
private fun removeSearchInIntent(intent: Intent) { private fun removeSearchInIntent(intent: Intent) {
@@ -893,24 +959,47 @@ class GroupActivity : LockingActivity(),
private val TAG = GroupActivity::class.java.name private val TAG = GroupActivity::class.java.name
private const val REQUEST_STARTUP_SEARCH_KEY = "REQUEST_STARTUP_SEARCH_KEY"
private const val GROUP_ID_KEY = "GROUP_ID_KEY" private const val GROUP_ID_KEY = "GROUP_ID_KEY"
private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG" private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG"
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 buildAndLaunchIntent(context: Context, group: GroupVersioned?, readOnly: Boolean, private fun buildIntent(context: Context,
intentBuildLauncher: (Intent) -> Unit) { group: Group?,
val checkTime = if (context is Activity) searchInfo: SearchInfo?,
TimeoutHelper.checkTimeAndLockIfTimeout(context) readOnly: Boolean,
else intentBuildLauncher: (Intent) -> Unit) {
TimeoutHelper.checkTime(context) val intent = Intent(context, GroupActivity::class.java)
if (checkTime) { if (group != null) {
val intent = Intent(context, GroupActivity::class.java) intent.putExtra(GROUP_ID_KEY, group.nodeId)
if (group != null) { }
intent.putExtra(GROUP_ID_KEY, group.nodeId) if (searchInfo != null) {
} intent.action = Intent.ACTION_SEARCH
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly) val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId
intentBuildLauncher.invoke(intent) intent.putExtra(SearchManager.QUERY, searchQuery)
}
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intentBuildLauncher.invoke(intent)
}
private fun checkTimeAndBuildIntent(activity: Activity,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
buildIntent(activity, group, searchInfo, readOnly, intentBuildLauncher)
}
}
private fun checkTimeAndBuildIntent(context: Context,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTime(context)) {
buildIntent(context, group, searchInfo, readOnly, intentBuildLauncher)
} }
} }
@@ -919,11 +1008,9 @@ class GroupActivity : LockingActivity(),
* Standard Launch * Standard Launch
* ------------------------- * -------------------------
*/ */
fun launch(context: Context,
@JvmOverloads readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
fun launch(context: Context, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) { checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
TimeoutHelper.recordTime(context)
buildAndLaunchIntent(context, null, readOnly) { intent ->
context.startActivity(intent) context.startActivity(intent)
} }
} }
@@ -934,10 +1021,9 @@ class GroupActivity : LockingActivity(),
* ------------------------- * -------------------------
*/ */
// TODO implement pre search to directly open the direct group // TODO implement pre search to directly open the direct group
fun launchForKeyboardSelection(context: Context,
fun launchForKeyboardSelection(context: Context, readOnly: Boolean) { readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
TimeoutHelper.recordTime(context) checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
buildAndLaunchIntent(context, null, readOnly) { intent ->
EntrySelectionHelper.startActivityForEntrySelection(context, intent) EntrySelectionHelper.startActivityForEntrySelection(context, intent)
} }
} }
@@ -947,13 +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, assistStructure: AssistStructure, readOnly: Boolean) { fun launchForAutofillResult(activity: Activity,
TimeoutHelper.recordTime(activity) assistStructure: AssistStructure,
buildAndLaunchIntent(activity, null, readOnly) { intent -> searchInfo: SearchInfo? = null,
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure) readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
checkTimeAndBuildIntent(activity, null, searchInfo, readOnly) { intent ->
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure, searchInfo)
} }
} }
} }

View File

@@ -1,10 +1,27 @@
/*
* 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.activities package com.kunzisoft.keepass.activities
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.util.Log import android.util.Log
@@ -18,16 +35,16 @@ import androidx.appcompat.view.ActionMode
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.NodeAdapter import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.NodeVersioned import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.activities.stylish.StylishFragment import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Type import com.kunzisoft.keepass.database.element.node.Type
import java.util.* import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener { class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
@@ -35,8 +52,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private var nodeClickListener: NodeClickListener? = null private var nodeClickListener: NodeClickListener? = null
private var onScrollListener: OnScrollListener? = null private var onScrollListener: OnScrollListener? = null
private var listView: RecyclerView? = null private var mNodesRecyclerView: RecyclerView? = null
var mainGroup: GroupVersioned? = null var mainGroup: Group? = null
private set private set
private var mAdapter: NodeAdapter? = null private var mAdapter: NodeAdapter? = null
@@ -44,14 +61,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private set private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
private set private set
private val listActionNodes = LinkedList<NodeVersioned>() private val listActionNodes = LinkedList<Node>()
private val listPasteNodes = LinkedList<NodeVersioned>() private val listPasteNodes = LinkedList<Node>()
private var notFoundView: View? = null private var notFoundView: View? = null
private var isASearchResult: Boolean = false private var isASearchResult: Boolean = false
// Preferences for sorting
private var prefs: SharedPreferences? = null
private var readOnly: Boolean = false private var readOnly: Boolean = false
get() { get() {
@@ -103,7 +118,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
mAdapter = NodeAdapter(context) mAdapter = NodeAdapter(context)
mAdapter?.apply { mAdapter?.apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback { setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(node: NodeVersioned) { override fun onNodeClick(node: Node) {
if (nodeActionSelectionMode) { if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) { if (listActionNodes.contains(node)) {
// Remove selected item if already selected // Remove selected item if already selected
@@ -120,7 +135,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
} }
override fun onNodeLongClick(node: NodeVersioned): Boolean { override fun onNodeLongClick(node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) { if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click // Select the first item after a long click
if (!listActionNodes.contains(node)) if (!listActionNodes.contains(node))
@@ -136,7 +151,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}) })
} }
} }
prefs = PreferenceManager.getDefaultSharedPreferences(context)
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@@ -150,11 +164,17 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// To apply theme // To apply theme
val rootView = inflater.cloneInContext(contextThemed) val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_list_nodes, container, false) .inflate(R.layout.fragment_list_nodes, container, false)
listView = rootView.findViewById(R.id.nodes_list) mNodesRecyclerView = rootView.findViewById(R.id.nodes_list)
notFoundView = rootView.findViewById(R.id.not_found_container) notFoundView = rootView.findViewById(R.id.not_found_container)
mNodesRecyclerView?.apply {
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
layoutManager = LinearLayoutManager(context)
adapter = mAdapter
}
onScrollListener?.let { onScrollListener -> onScrollListener?.let { onScrollListener ->
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() { mNodesRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
onScrollListener.onScrolled(dy) onScrollListener.onScrolled(dy)
@@ -162,8 +182,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}) })
} }
rebuildList()
return rootView return rootView
} }
@@ -175,14 +193,14 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
// Refresh data // Refresh data
mAdapter?.notifyDataSetChanged() rebuildList()
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) { if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found " // To show the " no search entry found "
listView?.visibility = View.GONE mNodesRecyclerView?.visibility = View.GONE
notFoundView?.visibility = View.VISIBLE notFoundView?.visibility = View.VISIBLE
} else { } else {
listView?.visibility = View.VISIBLE mNodesRecyclerView?.visibility = View.VISIBLE
notFoundView?.visibility = View.GONE notFoundView?.visibility = View.GONE
} }
} }
@@ -190,30 +208,27 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
fun rebuildList() { fun rebuildList() {
// Add elements to the list // Add elements to the list
mainGroup?.let { mainGroup -> mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup) mAdapter?.apply {
} rebuildList(mainGroup)
listView?.apply { // To visually change the elements
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET if (PreferencesUtil.APPEARANCE_CHANGED) {
layoutManager = LinearLayoutManager(context) notifyDataSetChanged()
adapter = mAdapter PreferencesUtil.APPEARANCE_CHANGED = false
}
}
} }
} }
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) { override fun onSortSelected(sortNodeEnum: SortNodeEnum,
// Toggle setting sortNodeParameters: SortNodeEnum.SortNodeParameters) {
prefs?.edit()?.apply { // Save setting
putString(getString(R.string.sort_node_key), sortNodeEnum.name) context?.let {
putBoolean(getString(R.string.sort_ascending_key), ascending) PreferencesUtil.saveNodeSort(it, sortNodeEnum, sortNodeParameters)
putBoolean(getString(R.string.sort_group_before_key), groupsBefore)
putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom)
apply()
} }
// Tell the adapter to refresh it's list // Tell the adapter to refresh it's list
mAdapter?.notifyChangeSort(sortNodeEnum, ascending, groupsBefore) mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
mainGroup?.let { mainGroup -> rebuildList()
mAdapter?.rebuildList(mainGroup)
}
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -228,8 +243,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
R.id.menu_sort -> { R.id.menu_sort -> {
context?.let { context -> context?.let { context ->
val sortDialogFragment: SortDialogFragment = val sortDialogFragment: SortDialogFragment =
if (Database.getInstance().allowRecycleBin if (Database.getInstance().isRecycleBinEnabled) {
&& Database.getInstance().isRecycleBinEnabled) {
SortDialogFragment.getInstance( SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context), PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context), PreferencesUtil.getAscendingSort(context),
@@ -251,7 +265,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
} }
fun actionNodesCallback(nodes: List<NodeVersioned>, fun actionNodesCallback(nodes: List<Node>,
menuListener: NodesActionMenuListener?) : ActionMode.Callback { menuListener: NodesActionMenuListener?) : ActionMode.Callback {
return object : ActionMode.Callback { return object : ActionMode.Callback {
@@ -276,7 +290,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Open and Edit for a single item // Open and Edit for a single item
if (nodes.size == 1) { if (nodes.size == 1) {
// Edition // Edition
if (readOnly || nodes[0] == database.recycleBin) { if (readOnly
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
menu?.removeItem(R.id.menu_edit) menu?.removeItem(R.id.menu_edit)
} }
} else { } else {
@@ -287,7 +302,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Copy and Move (not for groups) // Copy and Move (not for groups)
if (readOnly if (readOnly
|| isASearchResult || isASearchResult
|| nodes.any { it == database.recycleBin }
|| 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)
@@ -295,7 +309,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
// Deletion // Deletion
if (readOnly || nodes.any { it == database.recycleBin }) { if (readOnly
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
menu?.removeItem(R.id.menu_delete) menu?.removeItem(R.id.menu_delete)
} }
} }
@@ -354,46 +369,42 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> { EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) { || resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
data?.getParcelableExtra<NodeVersioned>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode -> data?.getParcelableExtra<Node>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { changedNode ->
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE) if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter?.addNode(newNode) addNode(changedNode)
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) { if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE)
//mAdapter.updateLastNodeRegister(newNode); mAdapter?.notifyDataSetChanged()
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
}
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result") } ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
} }
} }
} }
} }
fun contains(node: NodeVersioned): Boolean { fun contains(node: Node): Boolean {
return mAdapter?.contains(node) ?: false return mAdapter?.contains(node) ?: false
} }
fun addNode(newNode: NodeVersioned) { fun addNode(newNode: Node) {
mAdapter?.addNode(newNode) mAdapter?.addNode(newNode)
} }
fun addNodes(newNodes: List<NodeVersioned>) { fun addNodes(newNodes: List<Node>) {
mAdapter?.addNodes(newNodes) mAdapter?.addNodes(newNodes)
} }
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) { fun updateNode(oldNode: Node, newNode: Node? = null) {
mAdapter?.updateNode(oldNode, newNode ?: oldNode) mAdapter?.updateNode(oldNode, newNode ?: oldNode)
} }
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) { fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
mAdapter?.updateNodes(oldNodes, newNodes) mAdapter?.updateNodes(oldNodes, newNodes)
} }
fun removeNode(pwNode: NodeVersioned) { fun removeNode(pwNode: Node) {
mAdapter?.removeNode(pwNode) mAdapter?.removeNode(pwNode)
} }
fun removeNodes(nodes: List<NodeVersioned>) { fun removeNodes(nodes: List<Node>) {
mAdapter?.removeNodes(nodes) mAdapter?.removeNodes(nodes)
} }
@@ -409,20 +420,20 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
* Callback listener to redefine to do an action when a node is click * Callback listener to redefine to do an action when a node is click
*/ */
interface NodeClickListener { interface NodeClickListener {
fun onNodeClick(node: NodeVersioned) fun onNodeClick(node: Node)
fun onNodeSelected(nodes: List<NodeVersioned>): Boolean fun onNodeSelected(nodes: List<Node>): Boolean
} }
/** /**
* Menu listener to redefine to do an action in menu * Menu listener to redefine to do an action in menu
*/ */
interface NodesActionMenuListener { interface NodesActionMenuListener {
fun onOpenMenuClick(node: NodeVersioned): Boolean fun onOpenMenuClick(node: Node): Boolean
fun onEditMenuClick(node: NodeVersioned): Boolean fun onEditMenuClick(node: Node): Boolean
fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean fun onCopyMenuClick(nodes: List<Node>): Boolean
fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean fun onMoveMenuClick(nodes: List<Node>): Boolean
fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean fun onDeleteMenuClick(nodes: List<Node>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<NodeVersioned>): Boolean fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
} }
enum class PasteMode { enum class PasteMode {
@@ -447,7 +458,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private const val GROUP_KEY = "GROUP_KEY" private const val GROUP_KEY = "GROUP_KEY"
private const val IS_SEARCH = "IS_SEARCH" private const val IS_SEARCH = "IS_SEARCH"
fun newInstance(group: GroupVersioned?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment { fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
val bundle = Bundle() val bundle = Bundle()
if (group != null) { if (group != null) {
bundle.putParcelable(GROUP_KEY, group) bundle.putParcelable(GROUP_KEY, group)

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
@@ -23,31 +23,24 @@ 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.SharedPreferences 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
import android.os.Handler import android.os.Handler
import android.preference.PreferenceManager
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.*
import android.view.Menu
import android.view.MenuItem
import android.view.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
import com.kunzisoft.keepass.activities.dialogs.FingerPrintExplanationDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
@@ -56,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.LoadDatabaseDuplicateUuidException 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
@@ -72,47 +67,53 @@ 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
class PasswordActivity : StylishActivity() { open class PasswordActivity : StylishActivity() {
// Views // Views
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
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
private var checkboxDefaultDatabaseView: CompoundButton? = null private var checkboxDefaultDatabaseView: CompoundButton? = null
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
private var infoContainerView: ViewGroup? = null
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
private var mDatabaseFileUri: Uri? = null private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null private var mDatabaseKeyFileUri: Uri? = null
private var prefs: SharedPreferences? = null
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
set(value) {
infoContainerView?.visibility = if (value) {
readOnly = true
View.VISIBLE
} else {
View.GONE
}
field = value
}
private var progressDialogThread: ProgressDialogThread? = null private var mProgressDialogThread: ProgressDialogThread? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null private var advancedUnlockedManager: AdvancedUnlockedManager? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
prefs = PreferenceManager.getDefaultSharedPreferences(this)
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
setContentView(R.layout.activity_password) setContentView(R.layout.activity_password)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
@@ -122,20 +123,26 @@ class PasswordActivity : StylishActivity() {
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
containerView = findViewById(R.id.container) containerView = findViewById(R.id.container)
confirmButtonView = findViewById(R.id.pass_ok) 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.fingerprint_info) advancedUnlockInfoView = findViewById(R.id.biometric_info)
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 {
@@ -148,52 +155,32 @@ 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()
} }
progressDialogThread = ProgressDialogThread(this) { actionTask, result -> // If is a view intent
when (actionTask) { getUriFromIntent(intent)
ACTION_DATABASE_LOAD_TASK -> { if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
// Recheck fingerprint if error mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { }
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it mProgressDialogThread = ProgressDialogThread(this).apply {
advancedUnlockedManager?.initBiometricMode() onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck biometric if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
} }
}
var databaseUri: Uri? = null
var masterPassword: String? = null
var keyFileUri: Uri? = null
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
databaseUri?.let { databaseFileUri ->
// Remove the password in view in all cases
removePassword()
if (result.isSuccess) { if (result.isSuccess) {
mDatabaseKeyFileUri = null
clearCredentialsViews(true)
launchGroupActivity() launchGroupActivity()
} else { } else {
var resultError = "" var resultError = ""
@@ -202,24 +189,43 @@ class PasswordActivity : StylishActivity() {
if (resultException != null) { if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources) resultError = resultException.getLocalizedMessage(resources)
if (resultException is LoadDatabaseDuplicateUuidException)
// Relaunch loading if we need to fix UUID
if (resultException is DuplicateUuidDatabaseException) {
showLoadDatabaseDuplicateUuidMessage { showLoadDatabaseDuplicateUuidMessage {
showProgressDialogAndLoadDatabase(
databaseFileUri, var databaseUri: Uri? = null
masterPassword, var masterPassword: String? = null
keyFileUri, var keyFileUri: Uri? = null
readOnly, var readOnly = true
cipherEntity, var cipherEntity: CipherDatabaseEntity? = null
true)
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
masterPassword,
keyFileUri,
readOnly,
cipherEntity,
true)
}
} }
}
} }
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) { if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage" resultError = "$resultError $resultMessage"
} }
Log.e(TAG, resultError, resultException) Log.e(TAG, resultError, resultException)
Snackbar.make(activity_password_coordinator_layout, Snackbar.make(activity_password_coordinator_layout,
resultError, resultError,
Snackbar.LENGTH_LONG).asError().show() Snackbar.LENGTH_LONG).asError().show()
@@ -230,19 +236,59 @@ class PasswordActivity : StylishActivity() {
} }
} }
private fun getUriFromIntent(intent: Intent?) {
// If is a view intent
val action = intent?.action
if (action != null
&& action == VIEW_INTENT) {
mDatabaseFileUri = intent.data
mDatabaseKeyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
} else {
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
getUriFromIntent(intent)
}
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()
}
)
} }
}) })
} }
@@ -258,62 +304,61 @@ class PasswordActivity : StylishActivity() {
} }
override fun onResume() { override fun onResume() {
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) {
setEmptyViews() clearCredentialsViews()
} }
// For check shutdown // For check shutdown
super.onResume() super.onResume()
progressDialogThread?.registerProgressTask() mProgressDialogThread?.registerProgressTask()
initUriFromIntent() initUriFromIntent()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString())
}
ReadOnlyHelper.onSaveInstanceState(outState, readOnly) ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
private fun initUriFromIntent() { private fun initUriFromIntent() {
/*
val databaseUri: Uri? // "canXrite" doesn't work with Google Drive, don't really know why?
val keyFileUri: Uri? mForceReadOnly = mDatabaseFileUri?.let {
!FileDatabaseInfo(this, it).canWrite
// If is a view intent } ?: false
val action = intent.action */
if (action != null mForceReadOnly = mDatabaseFileUri?.let {
&& action == VIEW_INTENT) { !FileDatabaseInfo(this, it).exists
databaseUri = intent.data } ?: true
keyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
} else {
databaseUri = intent.getParcelableExtra(KEY_FILENAME)
keyFileUri = intent.getParcelableExtra(KEY_KEYFILE)
}
// Post init uri with KeyFile if needed // Post init uri with KeyFile if needed
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) { if (mRememberKeyFile && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
// Retrieve KeyFile in a thread // Retrieve KeyFile in a thread
databaseUri?.let { databaseUriNotNull -> mDatabaseFileUri?.let { databaseUri ->
FileDatabaseHistoryAction.getInstance(applicationContext) FileDatabaseHistoryAction.getInstance(applicationContext)
.getKeyFileUriByDatabaseUri(databaseUriNotNull) { .getKeyFileUriByDatabaseUri(databaseUri) {
onPostInitUri(databaseUri, it) onPostInitUri(databaseUri, it)
} }
} }
} else { } else {
onPostInitUri(databaseUri, keyFileUri) onPostInitUri(mDatabaseFileUri, mDatabaseKeyFileUri)
} }
} }
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) { private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
mDatabaseFileUri = databaseFileUri
mDatabaseKeyFileUri = keyFileUri
// Define title // Define title
databaseFileUri?.let { databaseFileUri?.let {
FileDatabaseInfo(this, it).retrieveDatabaseTitle { title -> FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
@@ -322,26 +367,18 @@ class PasswordActivity : StylishActivity() {
} }
// Define Key File text // Define Key File text
val keyUriString = keyFileUri?.toString() ?: "" if (mRememberKeyFile) {
if (keyUriString.isNotEmpty() && mRememberKeyFile) { // Bug KeepassDX #18 populateKeyFileTextView(keyFileUri)
populateKeyFileTextView(keyUriString)
} }
// Define listeners for default database checkbox and validate button // Define listeners for default database checkbox and validate button
checkboxDefaultDatabaseView?.setOnCheckedChangeListener { _, isChecked -> checkboxDefaultDatabaseView?.setOnCheckedChangeListener { _, isChecked ->
var newDefaultFileName: Uri? = null var newDefaultFileUri: Uri? = null
if (isChecked) { if (isChecked) {
newDefaultFileName = databaseFileUri ?: newDefaultFileName newDefaultFileUri = databaseFileUri ?: newDefaultFileUri
} }
prefs?.edit()?.apply { PreferencesUtil.saveDefaultDatabasePath(this, newDefaultFileUri)
newDefaultFileName?.let {
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
} ?: kotlin.run {
remove(KEY_DEFAULT_DATABASE_PATH)
}
apply()
}
val backupManager = BackupManager(this@PasswordActivity) val backupManager = BackupManager(this@PasswordActivity)
backupManager.dataChanged() backupManager.dataChanged()
@@ -349,7 +386,7 @@ class PasswordActivity : StylishActivity() {
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() } confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
// Retrieve settings for default database // Retrieve settings for default database
val defaultFilename = prefs?.getString(KEY_DEFAULT_DATABASE_PATH, "") val defaultFilename = PreferencesUtil.getDefaultDatabasePath(this)
if (databaseFileUri != null if (databaseFileUri != null
&& databaseFileUri.path != null && databaseFileUri.path!!.isNotEmpty() && databaseFileUri.path != null && databaseFileUri.path!!.isNotEmpty()
&& databaseFileUri == UriUtil.parse(defaultFilename)) { && databaseFileUri == UriUtil.parse(defaultFilename)) {
@@ -359,6 +396,8 @@ class PasswordActivity : StylishActivity() {
// If Activity is launch with a password and want to open directly // If Activity is launch with a password and want to open directly
val intent = intent val intent = intent
val password = intent.getStringExtra(KEY_PASSWORD) val password = intent.getStringExtra(KEY_PASSWORD)
// Consume the intent extra password
intent.removeExtra(KEY_PASSWORD)
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false) val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
if (password != null) { if (password != null) {
populatePasswordTextView(password) populatePasswordTextView(password)
@@ -366,15 +405,11 @@ class PasswordActivity : StylishActivity() {
if (launchImmediately) { if (launchImmediately) {
verifyCheckboxesAndLoadDatabase(password, keyFileUri) verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else { } else {
// Init FingerPrint elements // Init Biometric elements
var fingerPrintInit = false var biometricInitialize = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this)) { if (PreferencesUtil.isBiometricUnlockEnable(this)) {
advancedUnlockInfoView?.setOnClickListener {
FingerPrintExplanationDialog().show(supportFragmentManager, "fingerPrintExplanationDialog")
}
if (advancedUnlockedManager == null && databaseFileUri != null) { if (advancedUnlockedManager == null && databaseFileUri != null) {
advancedUnlockedManager = AdvancedUnlockedManager(this, advancedUnlockedManager = AdvancedUnlockedManager(this,
databaseFileUri, databaseFileUri,
@@ -396,18 +431,18 @@ class PasswordActivity : StylishActivity() {
{ passwordDecrypted -> { passwordDecrypted ->
// Load the database if password is retrieve from biometric // Load the database if password is retrieve from biometric
passwordDecrypted?.let { passwordDecrypted?.let {
// Retrieve from fingerprint // Retrieve from biometric
verifyKeyFileCheckboxAndLoadDatabase(it) verifyKeyFileCheckboxAndLoadDatabase(it)
} }
}) })
} }
advancedUnlockedManager?.initBiometric() advancedUnlockedManager?.checkBiometricAvailability()
fingerPrintInit = true biometricInitialize = true
} else { } else {
advancedUnlockedManager?.destroy() advancedUnlockedManager?.destroy()
} }
} }
if (!fingerPrintInit) { if (!biometricInitialize) {
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener) checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
} }
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener) checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
@@ -428,10 +463,9 @@ class PasswordActivity : StylishActivity() {
} }
} }
private fun setEmptyViews() { private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
populatePasswordTextView(null) populatePasswordTextView(null)
// Bug KeepassDX #18 if (clearKeyFile) {
if (!mRememberKeyFile) {
populateKeyFileTextView(null) populateKeyFileTextView(null)
} }
} }
@@ -448,24 +482,20 @@ 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
} }
} }
override fun onPause() { override fun onPause() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mProgressDialogThread?.unregisterProgressTask()
advancedUnlockedManager?.pause()
}
progressDialogThread?.unregisterProgressTask()
super.onPause() super.onPause()
} }
@@ -479,7 +509,7 @@ 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)
} }
@@ -492,7 +522,7 @@ 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)
} }
@@ -501,18 +531,13 @@ class PasswordActivity : StylishActivity() {
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
} }
private fun removePassword() {
passwordView?.setText("")
checkboxPasswordView?.isChecked = false
}
private fun loadDatabase(databaseFileUri: Uri?, private fun loadDatabase(databaseFileUri: Uri?,
password: String?, password: String?,
keyFileUri: Uri?, keyFileUri: Uri?,
cipherDatabaseEntity: CipherDatabaseEntity? = null) { cipherDatabaseEntity: CipherDatabaseEntity? = null) {
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) { if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
removePassword() clearCredentialsViews()
} }
databaseFileUri?.let { databaseUri -> databaseFileUri?.let { databaseUri ->
@@ -533,7 +558,7 @@ class PasswordActivity : StylishActivity() {
readOnly: Boolean, readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?, cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) { fixDuplicateUUID: Boolean) {
progressDialogThread?.startDatabaseLoad( mProgressDialogThread?.startDatabaseLoad(
databaseUri, databaseUri,
password, password,
keyFile, keyFile,
@@ -549,69 +574,116 @@ class PasswordActivity : StylishActivity() {
}.show(supportFragmentManager, "duplicateUUIDDialog") }.show(supportFragmentManager, "duplicateUUIDDialog")
} }
// To fix multiple view education
private var performedEductionInProgress = false
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater val inflater = menuInflater
// Read menu // Read menu
inflater.inflate(R.menu.open_file, menu) inflater.inflate(R.menu.open_file, menu)
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
if (mForceReadOnly) {
menu.removeItem(R.id.menu_open_file_read_mode_key)
} else {
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
}
MenuUtil.defaultMenuInflater(inflater, menu) MenuUtil.defaultMenuInflater(inflater, menu)
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Fingerprint menu // biometric menu
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu) advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
} }
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
if (!performedEductionInProgress) { launchEducation(menu) {
performedEductionInProgress = true launchCheckPermission()
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
} }
return true return true
} }
// Check permission
private fun launchCheckPermission() {
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
private var performedEductionInProgress = false
private fun launchEducation(menu: Menu, onEducationFinished: (()-> Unit)? = null) {
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
}
}
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation, private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu) { menu: Menu,
val educationContainerView = containerView onEducationFinished: (()-> Unit)? = null) {
val unlockEducationPerformed = educationContainerView != null val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation( && passwordActivityEducation.checkAndPerformedUnlockEducation(
educationContainerView, educationToolbar,
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}) })
if (!unlockEducationPerformed) { if (!unlockEducationPerformed) {
val educationToolbar = toolbar
val readOnlyEducationPerformed = val readOnlyEducationPerformed =
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation( && passwordActivityEducation.checkAndPerformedReadOnlyEducation(
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) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}) })
if (!readOnlyEducationPerformed) { if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate() val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
// fingerprintEducationPerformed 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.checkAndPerformedFingerprintEducation(advancedUnlockInfoView?.unlockIconImageView!!) && passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!biometricEducationPerformed) {
onEducationFinished?.invoke()
}
} }
} }
} }
@@ -634,7 +706,7 @@ class PasswordActivity : StylishActivity() {
readOnly = !readOnly readOnly = !readOnly
changeOpenFileReadIcon(item) changeOpenFileReadIcon(item)
} }
R.id.menu_fingerprint_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.deleteEntryKey() advancedUnlockedManager?.deleteEntryKey()
} }
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
@@ -659,7 +731,8 @@ class PasswordActivity : StylishActivity() {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
populateKeyFileTextView(uri.toString()) mDatabaseKeyFileUri = uri
populateKeyFileTextView(uri)
} }
} }
} }
@@ -667,7 +740,7 @@ class PasswordActivity : StylishActivity() {
// this block if not a key file response // this block if not a key file response
when (resultCode) { when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK, Activity.RESULT_CANCELED -> { LockingActivity.RESULT_EXIT_LOCK, Activity.RESULT_CANCELED -> {
setEmptyViews() clearCredentialsViews()
Database.getInstance().closeAndClear(applicationContext.filesDir) Database.getInstance().closeAndClear(applicationContext.filesDir)
} }
} }
@@ -678,14 +751,14 @@ class PasswordActivity : StylishActivity() {
private val TAG = PasswordActivity::class.java.name private val TAG = PasswordActivity::class.java.name
const val KEY_DEFAULT_DATABASE_PATH = "KEY_DEFAULT_DATABASE_PATH"
private const val KEY_FILENAME = "fileName" private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile" private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW" private const val VIEW_INTENT = "android.intent.action.VIEW"
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) {
@@ -740,13 +813,15 @@ 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

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
@@ -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

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

View File

@@ -0,0 +1,61 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.DatePickerDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment
class DatePickerFragment : DialogFragment() {
private var mDefaultYear: Int = 2000
private var mDefaultMonth: Int = 1
private var mDefaultDay: Int = 1
private var mListener: DatePickerDialog.OnDateSetListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as DatePickerDialog.OnDateSetListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it
return context?.let {
arguments?.apply {
if (containsKey(DEFAULT_YEAR_BUNDLE_KEY))
mDefaultYear = getInt(DEFAULT_YEAR_BUNDLE_KEY)
if (containsKey(DEFAULT_MONTH_BUNDLE_KEY))
mDefaultMonth = getInt(DEFAULT_MONTH_BUNDLE_KEY)
if (containsKey(DEFAULT_DAY_BUNDLE_KEY))
mDefaultDay = getInt(DEFAULT_DAY_BUNDLE_KEY)
}
DatePickerDialog(it, mListener, mDefaultYear, mDefaultMonth, mDefaultDay)
} ?: super.onCreateDialog(savedInstanceState)
}
companion object {
private const val DEFAULT_YEAR_BUNDLE_KEY = "DEFAULT_YEAR_BUNDLE_KEY"
private const val DEFAULT_MONTH_BUNDLE_KEY = "DEFAULT_MONTH_BUNDLE_KEY"
private const val DEFAULT_DAY_BUNDLE_KEY = "DEFAULT_DAY_BUNDLE_KEY"
fun getInstance(defaultYear: Int,
defaultMonth: Int,
defaultDay: Int): DatePickerFragment {
return DatePickerFragment().apply {
arguments = Bundle().apply {
putInt(DEFAULT_YEAR_BUNDLE_KEY, defaultYear)
putInt(DEFAULT_MONTH_BUNDLE_KEY, defaultMonth)
putInt(DEFAULT_DAY_BUNDLE_KEY, defaultDay)
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
class DeleteNodesDialogFragment : DialogFragment() {
private var mNodesToDelete: List<Node> = ArrayList()
private var mListener: DeleteNodeListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as DeleteNodeListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DeleteNodeListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
arguments?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), this)
}
} ?: savedInstanceState?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), savedInstanceState)
}
}
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
builder.setMessage(getString(R.string.warning_permanently_delete_nodes))
builder.setPositiveButton(android.R.string.yes) { _, _ ->
mListener?.permanentlyDeleteNodes(mNodesToDelete)
}
builder.setNegativeButton(android.R.string.no) { _, _ -> dismiss() }
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putAll(getBundleFromListNodes(mNodesToDelete))
}
interface DeleteNodeListener {
fun permanentlyDeleteNodes(nodes: List<Node>)
}
companion object {
fun getInstance(nodesToDelete: List<Node>): DeleteNodesDialogFragment {
return DeleteNodesDialogFragment().apply {
arguments = getBundleFromListNodes(nodesToDelete)
}
}
}
}

View File

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

View File

@@ -1,73 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintExplanationDialog : DialogFragment() {
private var fingerPrintAnimatedVector: FingerPrintAnimatedVector? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_fingerprint_explanation, null)
rootView.findViewById<View>(R.id.fingerprint_setting_link_text).setOnClickListener {
startActivity(Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS))
}
rootView.findViewById<View>(R.id.auto_open_biometric_prompt_button).setOnClickListener {
startActivity(Intent(activity, SettingsAdvancedUnlockActivity::class.java))
}
fingerPrintAnimatedVector = FingerPrintAnimatedVector(activity,
rootView.findViewById(R.id.biometric_image))
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
fingerPrintAnimatedVector?.startScan()
}
override fun onPause() {
super.onPause()
fingerPrintAnimatedVector?.stopScan()
}
}

View File

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

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
@@ -33,8 +33,8 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.PwIcon import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener { class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
@@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
private var editGroupDialogAction: EditGroupDialogAction? = null private var editGroupDialogAction: EditGroupDialogAction? = null
private var nameGroup: String? = null private var nameGroup: String? = null
private var iconGroup: PwIcon? = null private var iconGroup: IconImage? = null
private var nameTextLayoutView: TextInputLayout? = null private var nameTextLayoutView: TextInputLayout? = null
private var nameTextView: TextView? = null private var nameTextView: TextView? = null
@@ -186,8 +186,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
} }
interface EditGroupListener { interface EditGroupListener {
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?) fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?) fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
} }
companion object { companion object {
@@ -206,7 +206,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
return fragment return fragment
} }
fun build(group: GroupVersioned): GroupEditDialogFragment { fun build(group: Group): GroupEditDialogFragment {
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_NAME, group.title) bundle.putString(KEY_NAME, group.title)
bundle.putParcelable(KEY_ICON, group.icon) bundle.putParcelable(KEY_ICON, group.icon)

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
@@ -35,7 +35,7 @@ import android.widget.GridView
import android.widget.ImageView import android.widget.ImageView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.element.PwIconStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.icons.IconPack import com.kunzisoft.keepass.icons.IconPack
import com.kunzisoft.keepass.icons.IconPackChooser import com.kunzisoft.keepass.icons.IconPackChooser
@@ -72,7 +72,7 @@ class IconPickerDialogFragment : DialogFragment() {
currIconGridView.setOnItemClickListener { _, _, position, _ -> currIconGridView.setOnItemClickListener { _, _, position, _ ->
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position)) bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
iconPickerListener?.iconPicked(bundle) iconPickerListener?.iconPicked(bundle)
dismiss() dismiss()
} }
@@ -112,7 +112,7 @@ class IconPickerDialogFragment : DialogFragment() {
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
val ta = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor)) val ta = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
ImageViewCompat.setImageTintList(iconImageView, ColorStateList.valueOf(ta.getColor(0, Color.BLACK))) ImageViewCompat.setImageTintList(iconImageView, ColorStateList.valueOf(ta.getColor(0, Color.BLACK)))
ta?.recycle() ta.recycle()
} }
} }
@@ -128,7 +128,7 @@ class IconPickerDialogFragment : DialogFragment() {
private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD" private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD"
fun getIconStandardFromBundle(bundle: Bundle): PwIconStandard? { fun getIconStandardFromBundle(bundle: Bundle): IconImageStandard? {
return bundle.getParcelable(KEY_ICON_STANDARD) return bundle.getParcelable(KEY_ICON_STANDARD)
} }

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle
import android.provider.Settings
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
class KeyboardExplanationDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let {
val builder = AlertDialog.Builder(activity!!)
val inflater = activity!!.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_keyboard_explanation, null)
rootView.findViewById<View>(R.id.keyboards_activate_device_setting_button)
.setOnClickListener { launchActivateKeyboardSetting() }
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
if (BuildConfig.CLOSED_STORE) {
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
} else {
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
}
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun launchActivateKeyboardSetting() {
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}

View File

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

View File

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

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class ReadOnlyDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
var warning = getString(R.string.read_only_warning)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
}
builder.setMessage(warning)
builder.setPositiveButton(getString(android.R.string.ok)) { _, _ -> dismiss() }
builder.setNegativeButton(getString(R.string.beta_dontask)) { _, _ ->
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val edit = prefs.edit()
edit.putBoolean(getString(R.string.show_read_only_warning), false)
edit.apply()
dismiss()
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -0,0 +1,401 @@
/*
* 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.activities.dialogs
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Spinner
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.OtpModel
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_HOTP_COUNTER
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_OTP_DIGITS
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_HOTP_COUNTER
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_OTP_DIGITS
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpTokenType
import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.otp.TokenCalculator
import java.util.*
class SetOTPDialogFragment : DialogFragment() {
private var mCreateOTPElementListener: CreateOtpListener? = null
private var mOtpElement: OtpElement = OtpElement()
private var otpTypeSpinner: Spinner? = null
private var otpTokenTypeSpinner: Spinner? = null
private var otpSecretContainer: TextInputLayout? = null
private var otpSecretTextView: EditText? = null
private var otpPeriodContainer: TextInputLayout? = null
private var otpPeriodTextView: EditText? = null
private var otpCounterContainer: TextInputLayout? = null
private var otpCounterTextView: EditText? = null
private var otpDigitsContainer: TextInputLayout? = null
private var otpDigitsTextView: EditText? = null
private var otpAlgorithmSpinner: Spinner? = null
private var otpTypeAdapter: ArrayAdapter<OtpType>? = null
private var otpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var totpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var hotpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var otpAlgorithmAdapter: ArrayAdapter<TokenCalculator.HashAlgorithm>? = null
private var mManualEvent = false
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
if (!isFocus)
mManualEvent = true
}
private var mOnTouchListener = View.OnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mManualEvent = true
}
}
false
}
private var mSecretWellFormed = false
private var mCounterWellFormed = true
private var mPeriodWellFormed = true
private var mDigitsWellFormed = true
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
mCreateOTPElementListener = context as CreateOtpListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + CreateOtpListener::class.java.name)
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Retrieve OTP model from instance state
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(KEY_OTP)) {
savedInstanceState.getParcelable<OtpModel>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel)
}
}
} else {
arguments?.apply {
if (containsKey(KEY_OTP)) {
getParcelable<OtpModel?>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel)
}
}
}
}
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
otpSecretTextView = root?.findViewById(R.id.setup_otp_secret)
otpAlgorithmSpinner = root?.findViewById(R.id.setup_otp_algorithm)
otpPeriodContainer= root?.findViewById(R.id.setup_otp_period_label)
otpPeriodTextView = root?.findViewById(R.id.setup_otp_period)
otpCounterContainer= root?.findViewById(R.id.setup_otp_counter_label)
otpCounterTextView = root?.findViewById(R.id.setup_otp_counter)
otpDigitsContainer = root?.findViewById(R.id.setup_otp_digits_label)
otpDigitsTextView = root?.findViewById(R.id.setup_otp_digits)
// To fix init element
// With tab keyboard selection
otpSecretTextView?.onFocusChangeListener = mOnFocusChangeListener
// With finger selection
otpTypeSpinner?.setOnTouchListener(mOnTouchListener)
otpTokenTypeSpinner?.setOnTouchListener(mOnTouchListener)
otpSecretTextView?.setOnTouchListener(mOnTouchListener)
otpAlgorithmSpinner?.setOnTouchListener(mOnTouchListener)
otpPeriodTextView?.setOnTouchListener(mOnTouchListener)
otpCounterTextView?.setOnTouchListener(mOnTouchListener)
otpDigitsTextView?.setOnTouchListener(mOnTouchListener)
// HOTP / TOTP Type selection
val otpTypeArray = OtpType.values()
otpTypeAdapter = ArrayAdapter<OtpType>(activity,
android.R.layout.simple_spinner_item, otpTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTypeSpinner?.adapter = otpTypeAdapter
// Otp Token type selection
val hotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
hotpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, hotpTokenTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
// Proprietary only on closed and full version
val totpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
totpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, totpTokenTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTokenTypeAdapter = hotpTokenTypeAdapter
otpTokenTypeSpinner?.adapter = otpTokenTypeAdapter
// OTP Algorithm
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
otpAlgorithmAdapter = ArrayAdapter<TokenCalculator.HashAlgorithm>(activity,
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpAlgorithmSpinner?.adapter = otpAlgorithmAdapter
// Set the default value of OTP element
upgradeType()
upgradeTokenType()
upgradeParameters()
attachListeners()
val builder = AlertDialog.Builder(activity)
builder.apply {
setTitle(R.string.entry_setup_otp)
setView(root)
.setPositiveButton(android.R.string.ok) {_, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ ->
}
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
(dialog as AlertDialog).getButton(Dialog.BUTTON_POSITIVE).setOnClickListener {
if (mSecretWellFormed
&& mCounterWellFormed
&& mPeriodWellFormed
&& mDigitsWellFormed) {
mCreateOTPElementListener?.onOtpCreated(mOtpElement)
dismiss()
}
}
}
private fun attachListeners() {
// Set Type listener
otpTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as OtpType?)?.let {
mOtpElement.type = it
upgradeTokenType()
}
}
}
}
// Set type token listener
otpTokenTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as OtpTokenType?)?.let {
mOtpElement.tokenType = it
upgradeParameters()
}
}
}
}
// Set algorithm spinner
otpAlgorithmSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as TokenCalculator.HashAlgorithm?)?.let {
mOtpElement.algorithm = it
}
}
}
}
// Set secret in OtpElement
otpSecretTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
s?.toString()?.let { userString ->
try {
mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH))
otpSecretContainer?.error = null
} catch (exception: Exception) {
otpSecretContainer?.error = getString(R.string.error_otp_secret_key)
}
mSecretWellFormed = otpSecretContainer?.error == null
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set counter in OtpElement
otpCounterTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toLongOrNull()?.let {
try {
mOtpElement.counter = it
otpCounterContainer?.error = null
} catch (exception: Exception) {
otpCounterContainer?.error = getString(R.string.error_otp_counter,
MIN_HOTP_COUNTER, MAX_HOTP_COUNTER)
}
mCounterWellFormed = otpCounterContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set period in OtpElement
otpPeriodTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toIntOrNull()?.let {
try {
mOtpElement.period = it
otpPeriodContainer?.error = null
} catch (exception: Exception) {
otpPeriodContainer?.error = getString(R.string.error_otp_period,
MIN_TOTP_PERIOD, MAX_TOTP_PERIOD)
}
mPeriodWellFormed = otpPeriodContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set digits in OtpElement
otpDigitsTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toIntOrNull()?.let {
try {
mOtpElement.digits = it
otpDigitsContainer?.error = null
} catch (exception: Exception) {
otpDigitsContainer?.error = getString(R.string.error_otp_digits,
MIN_OTP_DIGITS, MAX_OTP_DIGITS)
}
mDigitsWellFormed = otpDigitsContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
}
private fun upgradeType() {
otpTypeSpinner?.setSelection(OtpType.values().indexOf(mOtpElement.type))
}
private fun upgradeTokenType() {
when (mOtpElement.type) {
OtpType.HOTP -> {
otpPeriodContainer?.visibility = View.GONE
otpCounterContainer?.visibility = View.VISIBLE
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
otpTokenTypeSpinner?.setSelection(OtpTokenType
.getHotpTokenTypeValues().indexOf(mOtpElement.tokenType))
}
OtpType.TOTP -> {
otpPeriodContainer?.visibility = View.VISIBLE
otpCounterContainer?.visibility = View.GONE
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
otpTokenTypeSpinner?.setSelection(OtpTokenType
.getTotpTokenTypeValues().indexOf(mOtpElement.tokenType))
}
}
}
private fun upgradeParameters() {
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
.indexOf(mOtpElement.algorithm))
otpSecretTextView?.apply {
setText(mOtpElement.getBase32Secret())
// Cursor at end
setSelection(this.text.length)
}
otpCounterTextView?.setText(mOtpElement.counter.toString())
otpPeriodTextView?.setText(mOtpElement.period.toString())
otpDigitsTextView?.setText(mOtpElement.digits.toString())
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(KEY_OTP, mOtpElement.otpModel)
}
interface CreateOtpListener {
fun onOtpCreated(otpElement: OtpElement)
}
companion object {
private const val KEY_OTP = "KEY_OTP"
fun build(otpModel: OtpModel? = null): SetOTPDialogFragment {
return SetOTPDialogFragment().apply {
if (otpModel != null) {
arguments = Bundle().apply {
putParcelable(KEY_OTP, otpModel)
}
}
}
}
}
}

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
@@ -29,7 +29,7 @@ import android.view.View
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.RadioGroup import android.widget.RadioGroup
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
class SortDialogFragment : DialogFragment() { class SortDialogFragment : DialogFragment() {
@@ -82,7 +82,9 @@ class SortDialogFragment : DialogFragment() {
builder.setView(rootView) builder.setView(rootView)
// Add action buttons // Add action buttons
.setPositiveButton(android.R.string.ok .setPositiveButton(android.R.string.ok
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) } ) { _, _ -> mListener?.onSortSelected(mSortNodeEnum,
SortNodeEnum.SortNodeParameters(mAscending, mGroupsBefore, mRecycleBinBottom))
}
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending) val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
@@ -150,10 +152,7 @@ class SortDialogFragment : DialogFragment() {
} }
interface SortSelectionListener { interface SortSelectionListener {
fun onSortSelected(sortNodeEnum: SortNodeEnum, fun onSortSelected(sortNodeEnum: SortNodeEnum, sortNodeParameters: SortNodeEnum.SortNodeParameters)
ascending: Boolean,
groupsBefore: Boolean,
recycleBinBottom: Boolean)
} }
companion object { companion object {

View File

@@ -0,0 +1,57 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.DatePickerDialog
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.Context
import android.os.Bundle
import android.text.format.DateFormat
import androidx.fragment.app.DialogFragment
class TimePickerFragment : DialogFragment() {
private var defaultHour: Int = 0
private var defaultMinute: Int = 0
private var mListener: TimePickerDialog.OnTimeSetListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as TimePickerDialog.OnTimeSetListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it
return context?.let {
arguments?.apply {
if (containsKey(DEFAULT_HOUR_BUNDLE_KEY))
defaultHour = getInt(DEFAULT_HOUR_BUNDLE_KEY)
if (containsKey(DEFAULT_MINUTE_BUNDLE_KEY))
defaultMinute = getInt(DEFAULT_MINUTE_BUNDLE_KEY)
}
TimePickerDialog(it, mListener, defaultHour, defaultMinute, DateFormat.is24HourFormat(activity))
} ?: super.onCreateDialog(savedInstanceState)
}
companion object {
private const val DEFAULT_HOUR_BUNDLE_KEY = "DEFAULT_HOUR_BUNDLE_KEY"
private const val DEFAULT_MINUTE_BUNDLE_KEY = "DEFAULT_MINUTE_BUNDLE_KEY"
fun getInstance(defaultHour: Int,
defaultMinute: Int): TimePickerFragment {
return TimePickerFragment().apply {
arguments = Bundle().apply {
putInt(DEFAULT_HOUR_BUNDLE_KEY, defaultHour)
putInt(DEFAULT_MINUTE_BUNDLE_KEY, defaultMinute)
}
}
}
}
}

View File

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

View File

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

View File

@@ -1,3 +1,22 @@
/*
* 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.activities.helpers package com.kunzisoft.keepass.activities.helpers
import android.app.assist.AssistStructure import android.app.assist.AssistStructure

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.helpers package com.kunzisoft.keepass.activities.helpers
@@ -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")
@@ -75,9 +92,10 @@ class OpenFileHelper {
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply { val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*" type = "*/*"
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
} }
if (fragment != null) if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC) fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
@@ -85,10 +103,15 @@ class OpenFileHelper {
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC) activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
} }
@SuppressLint("InlinedApi")
private fun openActivityWithActionGetContent() { private fun openActivityWithActionGetContent() {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply { val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*" type = "*/*"
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
} }
if (fragment != null) if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT) fragment?.startActivityForResult(intentGetContent, GET_CONTENT)

View File

@@ -1,3 +1,22 @@
/*
* 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.activities.helpers package com.kunzisoft.keepass.activities.helpers
import android.content.Context import android.content.Context

View File

@@ -1,30 +1,26 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.lock package com.kunzisoft.keepass.activities.lock
import android.app.Activity import android.app.Activity
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
@@ -32,12 +28,11 @@ import android.view.ViewGroup
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
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.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.LOCK_ACTION import com.kunzisoft.keepass.utils.*
abstract class LockingActivity : StylishActivity() { abstract class LockingActivity : StylishActivity() {
@@ -62,6 +57,10 @@ abstract class LockingActivity : StylishActivity() {
return field || mSelectionMode return field || mSelectionMode
} }
protected var mSelectionMode: Boolean = false protected var mSelectionMode: Boolean = false
protected var mAutoSaveEnable: Boolean = true
var mProgressDialogThread: ProgressDialogThread? = null
private set
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -75,16 +74,16 @@ abstract class LockingActivity : StylishActivity() {
} }
if (mTimeoutEnable) { if (mTimeoutEnable) {
mLockReceiver = LockReceiver() mLockReceiver = LockReceiver {
val intentFilter = IntentFilter().apply { lockAndExit()
addAction(Intent.ACTION_SCREEN_OFF)
addAction(LOCK_ACTION)
} }
registerReceiver(mLockReceiver, intentFilter) registerLockReceiver(mLockReceiver)
} }
mExitLock = false mExitLock = false
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent) mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
mProgressDialogThread = ProgressDialogThread(this)
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -100,8 +99,13 @@ abstract class LockingActivity : StylishActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
mProgressDialogThread?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow // To refresh when back to normal workflow from selection workflow
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent) mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
invalidateOptionsMenu()
if (mTimeoutEnable) { if (mTimeoutEnable) {
// End activity if database not loaded // End activity if database not loaded
@@ -118,8 +122,6 @@ abstract class LockingActivity : StylishActivity() {
if (!mExitLock) if (!mExitLock)
TimeoutHelper.recordTime(this) TimeoutHelper.recordTime(this)
} }
invalidateOptionsMenu()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@@ -129,6 +131,8 @@ abstract class LockingActivity : StylishActivity() {
} }
override fun onPause() { override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
super.onPause() super.onPause()
if (mTimeoutEnable) { if (mTimeoutEnable) {
@@ -138,29 +142,12 @@ abstract class LockingActivity : StylishActivity() {
} }
override fun onDestroy() { override fun onDestroy() {
unregisterLockReceiver(mLockReceiver)
super.onDestroy() super.onDestroy()
if (mLockReceiver != null)
unregisterReceiver(mLockReceiver)
}
inner class LockReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// If allowed, lock and exit
if (!TimeoutHelper.temporarilyDisableTimeout) {
intent.action?.let {
when (it) {
Intent.ACTION_SCREEN_OFF -> if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this@LockingActivity)) {
lockAndExit()
}
LOCK_ACTION -> lockAndExit()
}
}
}
}
} }
protected fun lockAndExit() { protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION))
lock() lock()
} }
@@ -195,17 +182,8 @@ abstract class LockingActivity : StylishActivity() {
} }
fun Activity.lock() { fun Activity.lock() {
// Stop the Magikeyboard service closeDatabase()
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
MagikIME.removeEntry(this)
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
" after inactivity or manual lock")
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
cancelAll()
}
// Clear data
Database.getInstance().closeAndClear(applicationContext.filesDir)
// Add onActivityForResult response // Add onActivityForResult response
setResult(LockingActivity.RESULT_EXIT_LOCK) setResult(LockingActivity.RESULT_EXIT_LOCK)
finish() finish()

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.lock
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
/**
* Locking Hide Activity that sets FLAG_SECURE to prevent screenshots, and from
* appearing in the recent app preview
*/
abstract class LockingHideActivity : LockingActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
}

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.stylish package com.kunzisoft.keepass.activities.stylish
@@ -61,6 +61,7 @@ object Stylish {
return when (themeString) { return when (themeString) {
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red

View File

@@ -1,38 +1,63 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.activities.stylish package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.util.Log import android.util.Log
import android.view.WindowManager
/**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
* appearing in the recent app preview
*/
abstract class StylishActivity : AppCompatActivity() { abstract class StylishActivity : AppCompatActivity() {
@StyleRes @StyleRes
private var themeId: Int = 0 private var themeId: Int = 0
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
this.themeId = Stylish.getThemeId(this) this.themeId = Stylish.getThemeId(this)
setTheme(themeId) setTheme(themeId)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
} }
override fun onResume() { override fun onResume() {

View File

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

View File

@@ -0,0 +1,100 @@
/*
* 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.adapters
import android.content.Context
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<EntryAttachmentsAdapter.EntryBinariesViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList()
var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null
private val mDatabase = Database.getInstance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
}
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
val entryAttachment = entryAttachmentsList[position]
holder.binaryFileTitle.text = entryAttachment.name
holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachment.binaryAttachment.length())
holder.binaryFileCompression.apply {
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
|| entryAttachment.binaryAttachment.isCompressed == true) {
text = CompressionAlgorithm.GZip.getName(context.resources)
visibility = View.VISIBLE
} else {
text = ""
visibility = View.GONE
}
}
holder.binaryFileProgress.apply {
visibility = when (entryAttachment.downloadState) {
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
}
progress = entryAttachment.downloadProgression
}
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryAttachment, position)
}
}
override fun getItemCount(): Int {
return entryAttachmentsList.size
}
fun updateProgress(entryAttachment: EntryAttachment) {
val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name }
if (indexEntryAttachment != -1) {
entryAttachmentsList[indexEntryAttachment] = entryAttachment
notifyItemChanged(indexEntryAttachment)
}
}
fun clear() {
entryAttachmentsList.clear()
}
inner class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
}
}

View File

@@ -1,3 +1,22 @@
/*
* 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.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
@@ -7,13 +26,13 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.EntryVersioned import com.kunzisoft.keepass.database.element.Entry
class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() { class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryHistoryList: MutableList<EntryVersioned> = ArrayList() var entryHistoryList: MutableList<Entry> = ArrayList()
var onItemClickListener: ((item: EntryVersioned, position: Int)->Unit)? = null var onItemClickListener: ((item: Entry, position: Int)->Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false)) return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false))

View File

@@ -1,3 +1,22 @@
/*
* 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.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
@@ -15,10 +34,9 @@ import java.util.ArrayList
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() { class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
var fields: MutableList<Field> = ArrayList() private var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false) val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
return FieldViewHolder(view) return FieldViewHolder(view)
@@ -34,6 +52,11 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
return fields.size return fields.size
} }
fun setFields(fieldsToAdd: List<Field>) {
fields.clear()
fields.addAll(fieldsToAdd)
}
fun clear() { fun clear() {
fields.clear() fields.clear()
} }

View File

@@ -1,25 +1,27 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.util.TypedValue import android.util.TypedValue
@@ -82,15 +84,27 @@ class FileDatabaseHistoryAdapter(private val context: Context)
// File path // File path
holder.filePath.text = UriUtil.decode(fileDatabaseInfo.fileUri?.toString()) holder.filePath.text = UriUtil.decode(fileDatabaseInfo.fileUri?.toString())
holder.filePreciseInfoContainer.visibility = if (fileDatabaseInfo.found()) { if (fileDatabaseInfo.exists) {
// Modification holder.fileInformation.clearColorFilter()
holder.fileModification.text = fileDatabaseInfo.getModificationString() } else {
// Size holder.fileInformation.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)
holder.fileSize.text = fileDatabaseInfo.getSizeString() }
View.VISIBLE // Modification
} else fileDatabaseInfo.getModificationString()?.let {
View.GONE holder.fileModification.text = it
holder.fileModification.visibility = View.VISIBLE
} ?: run {
holder.fileModification.visibility = View.GONE
}
// Size
fileDatabaseInfo.getSizeString()?.let {
holder.fileSize.text = it
holder.fileSize.visibility = View.VISIBLE
} ?: run {
holder.fileSize.visibility = View.GONE
}
// Click on information // Click on information
val isExpanded = position == mExpandedPosition val isExpanded = position == mExpandedPosition
@@ -142,6 +156,10 @@ class FileDatabaseHistoryAdapter(private val context: Context)
return listDatabaseFiles.size return listDatabaseFiles.size
} }
fun clearDatabaseFileHistoryList() {
listDatabaseFiles.clear()
}
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) { fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
listDatabaseFiles.clear() listDatabaseFiles.clear()
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd) listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
@@ -178,7 +196,6 @@ class FileDatabaseHistoryAdapter(private val context: Context)
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button) var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button) var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
var filePath: TextView = itemView.findViewById(R.id.file_path) var filePath: TextView = itemView.findViewById(R.id.file_path)
var filePreciseInfoContainer: ViewGroup = itemView.findViewById(R.id.file_precise_info_container)
var fileModification: TextView = itemView.findViewById(R.id.file_modification) var fileModification: TextView = itemView.findViewById(R.id.file_modification)
var fileSize: TextView = itemView.findViewById(R.id.file_size) var fileSize: TextView = itemView.findViewById(R.id.file_size)
} }

View File

@@ -1,71 +1,74 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.Paint
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback import androidx.recyclerview.widget.SortedListAdapterCallback
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.SortNodeEnum import com.kunzisoft.keepass.database.element.Database
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.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type
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.setTextSize
import com.kunzisoft.keepass.view.strikeOut
import java.util.* import java.util.*
class NodeAdapter
/** /**
* Create node list adapter with contextMenu or not * Create node list adapter with contextMenu or not
* @param context Context to use * @param context Context to use
*/ */
(private val context: Context) class NodeAdapter (private val context: Context)
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() { : RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
private val nodeSortedList: SortedList<NodeVersioned> private var nodeComparator: Comparator<NodeVersionedInterface<Group>>? = null
private val nodeSortedListCallback: NodeSortedListCallback
private val nodeSortedList: SortedList<Node>
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
private var calculateViewTypeTextSize = Array(2) { true} // number of view type private var calculateViewTypeTextSize = Array(2) { true} // number of view type
private var textSizeUnit: Int = TypedValue.COMPLEX_UNIT_PX private var textSizeUnit: Int = TypedValue.COMPLEX_UNIT_PX
private var prefTextSize: Float = 0F private var prefSizeMultiplier: Float = 0F
private var subtextSize: Float = 0F private var subtextDefaultDimension: Float = 0F
private var infoTextSize: Float = 0F private var infoTextDefaultDimension: Float = 0F
private var numberChildrenTextSize: Float = 0F private var numberChildrenTextDefaultDimension: Float = 0F
private var iconSize: Float = 0F private var iconDefaultDimension: Float = 0F
private var listSort: SortNodeEnum = SortNodeEnum.DB
private var ascendingSort: Boolean = true
private var groupsBeforeSort: Boolean = true
private var recycleBinBottomSort: Boolean = true
private var showUserNames: Boolean = true private var showUserNames: Boolean = true
private var showNumberEntries: Boolean = true private var showNumberEntries: Boolean = true
private var entryFilters = arrayOf<Group.ChildFilter>()
private var actionNodesList = LinkedList<NodeVersioned>() private var actionNodesList = LinkedList<Node>()
private var nodeClickCallback: NodeClickCallback? = null private var nodeClickCallback: NodeClickCallback? = null
private val mDatabase: Database private val mDatabase: Database
@@ -81,23 +84,15 @@ class NodeAdapter
get() = nodeSortedList.size() <= 0 get() = nodeSortedList.size() <= 0
init { init {
this.infoTextDefaultDimension = context.resources.getDimension(R.dimen.list_medium_size_default)
this.subtextDefaultDimension = context.resources.getDimension(R.dimen.list_small_size_default)
this.numberChildrenTextDefaultDimension = context.resources.getDimension(R.dimen.list_tiny_size_default)
this.iconDefaultDimension = context.resources.getDimension(R.dimen.list_icon_size_default)
assignPreferences() assignPreferences()
this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback<NodeVersioned>(this) { this.nodeSortedListCallback = NodeSortedListCallback()
override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int { this.nodeSortedList = SortedList(Node::class.java, nodeSortedListCallback)
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
}
override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean {
return oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
}
override fun areItemsTheSame(item1: NodeVersioned, item2: NodeVersioned): Boolean {
return item1 == item2
}
})
// Database // Database
this.mDatabase = Database.getInstance() this.mDatabase = Database.getInstance()
@@ -112,20 +107,23 @@ class NodeAdapter
taTextColor.recycle() taTextColor.recycle()
} }
private fun assignPreferences() { fun assignPreferences() {
this.prefTextSize = PreferencesUtil.getListTextSize(context) this.prefSizeMultiplier = PreferencesUtil.getListTextSize(context)
this.infoTextSize = context.resources.getDimension(R.dimen.list_medium_size_default) * prefTextSize
this.subtextSize = context.resources.getDimension(R.dimen.list_small_size_default) * prefTextSize notifyChangeSort(
this.numberChildrenTextSize = context.resources.getDimension(R.dimen.list_tiny_size_default) * prefTextSize PreferencesUtil.getListSort(context),
this.iconSize = context.resources.getDimension(R.dimen.list_icon_size_default) * prefTextSize SortNodeEnum.SortNodeParameters(
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context)
)
)
this.listSort = PreferencesUtil.getListSort(context)
this.ascendingSort = PreferencesUtil.getAscendingSort(context)
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context)
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context) this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.showNumberEntries = PreferencesUtil.showNumberEntries(context) this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
this.entryFilters = Group.ChildFilter.getDefaults(context)
// Reinit textSize for all view type // Reinit textSize for all view type
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true } calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
} }
@@ -133,19 +131,29 @@ class NodeAdapter
/** /**
* Rebuild the list by clear and build children from the group * Rebuild the list by clear and build children from the group
*/ */
fun rebuildList(group: GroupVersioned) { fun rebuildList(group: Group) {
this.nodeSortedList.clear()
assignPreferences() assignPreferences()
try { nodeSortedList.replaceAll(group.getFilteredChildren(*entryFilters)
this.nodeSortedList.addAll(group.getChildren()) )
} catch (e: Exception) {
Log.e(TAG, "Can't add node elements to the list", e)
Toast.makeText(context, "Can't add node elements to the list : " + e.message, Toast.LENGTH_LONG).show()
}
notifyDataSetChanged()
} }
fun contains(node: NodeVersioned): Boolean { private inner class NodeSortedListCallback: SortedListAdapterCallback<Node>(this) {
override fun compare(item1: Node, item2: Node): Int {
return nodeComparator!!.compare(item1, item2)
}
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
return oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
}
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
return item1 == item2
}
}
fun contains(node: Node): Boolean {
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
} }
@@ -153,7 +161,7 @@ class NodeAdapter
* Add a node to the list * Add a node to the list
* @param node Node to add * @param node Node to add
*/ */
fun addNode(node: NodeVersioned) { fun addNode(node: Node) {
nodeSortedList.add(node) nodeSortedList.add(node)
} }
@@ -161,7 +169,7 @@ class NodeAdapter
* Add nodes to the list * Add nodes to the list
* @param nodes Nodes to add * @param nodes Nodes to add
*/ */
fun addNodes(nodes: List<NodeVersioned>) { fun addNodes(nodes: List<Node>) {
nodeSortedList.addAll(nodes) nodeSortedList.addAll(nodes)
} }
@@ -169,7 +177,7 @@ class NodeAdapter
* Remove a node in the list * Remove a node in the list
* @param node Node to delete * @param node Node to delete
*/ */
fun removeNode(node: NodeVersioned) { fun removeNode(node: Node) {
nodeSortedList.remove(node) nodeSortedList.remove(node)
} }
@@ -177,7 +185,7 @@ class NodeAdapter
* Remove nodes in the list * Remove nodes in the list
* @param nodes Nodes to delete * @param nodes Nodes to delete
*/ */
fun removeNodes(nodes: List<NodeVersioned>) { fun removeNodes(nodes: List<Node>) {
nodes.forEach { node -> nodes.forEach { node ->
nodeSortedList.remove(node) nodeSortedList.remove(node)
} }
@@ -209,7 +217,7 @@ class NodeAdapter
* @param oldNode Node before the update * @param oldNode Node before the update
* @param newNode Node after the update * @param newNode Node after the update
*/ */
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) { fun updateNode(oldNode: Node, newNode: Node) {
nodeSortedList.beginBatchedUpdates() nodeSortedList.beginBatchedUpdates()
nodeSortedList.remove(oldNode) nodeSortedList.remove(oldNode)
nodeSortedList.add(newNode) nodeSortedList.add(newNode)
@@ -221,7 +229,7 @@ class NodeAdapter
* @param oldNodes Nodes before the update * @param oldNodes Nodes before the update
* @param newNodes Node after the update * @param newNodes Node after the update
*/ */
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) { fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
nodeSortedList.beginBatchedUpdates() nodeSortedList.beginBatchedUpdates()
oldNodes.forEach { oldNode -> oldNodes.forEach { oldNode ->
nodeSortedList.remove(oldNode) nodeSortedList.remove(oldNode)
@@ -230,11 +238,11 @@ class NodeAdapter
nodeSortedList.endBatchedUpdates() nodeSortedList.endBatchedUpdates()
} }
fun notifyNodeChanged(node: NodeVersioned) { fun notifyNodeChanged(node: Node) {
notifyItemChanged(nodeSortedList.indexOf(node)) notifyItemChanged(nodeSortedList.indexOf(node))
} }
fun setActionNodes(actionNodes: List<NodeVersioned>) { fun setActionNodes(actionNodes: List<Node>) {
this.actionNodesList.apply { this.actionNodesList.apply {
clear() clear()
addAll(actionNodes) addAll(actionNodes)
@@ -253,10 +261,9 @@ class NodeAdapter
/** /**
* Notify a change sort of the list * Notify a change sort of the list
*/ */
fun notifyChangeSort(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean) { fun notifyChangeSort(sortNodeEnum: SortNodeEnum,
this.listSort = sortNodeEnum sortNodeParameters: SortNodeEnum.SortNodeParameters) {
this.ascendingSort = ascending this.nodeComparator = sortNodeEnum.getNodeComparator(sortNodeParameters)
this.groupsBeforeSort = groupsBefore
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
@@ -283,19 +290,57 @@ class NodeAdapter
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor) assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
// Relative size of the icon // Relative size of the icon
layoutParams?.apply { layoutParams?.apply {
height = iconSize.toInt() height = (iconDefaultDimension * prefSizeMultiplier).toInt()
width = iconSize.toInt() width = (iconDefaultDimension * prefSizeMultiplier).toInt()
} }
} }
// Assign text // Assign text
holder.text.apply { holder.text.apply {
text = subNode.title text = subNode.title
setTextSize(textSizeUnit, infoTextSize) setTextSize(textSizeUnit, infoTextDefaultDimension, prefSizeMultiplier)
paintFlags = if (subNode.isCurrentlyExpires) strikeOut(subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
} }
// Add subText with username
holder.subText.apply {
text = ""
strikeOut(subNode.isCurrentlyExpires)
visibility = View.GONE
}
// Specific elements for entry
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
holder.subText.apply {
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextDefaultDimension, prefSizeMultiplier)
}
}
mDatabase.stopManageEntry(entry)
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group)
.getNumberOfChildEntries(*entryFilters)
.toString()
setTextSize(textSizeUnit, numberChildrenTextDefaultDimension, prefSizeMultiplier)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
// Assign click // Assign click
holder.container.setOnClickListener { holder.container.setOnClickListener {
nodeClickCallback?.onNodeClick(subNode) nodeClickCallback?.onNodeClick(subNode)
@@ -305,45 +350,6 @@ class NodeAdapter
} }
holder.container.isSelected = actionNodesList.contains(subNode) holder.container.isSelected = actionNodesList.contains(subNode)
// Add subText with username
holder.subText.apply {
text = ""
paintFlags = if (subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
visibility = View.GONE
if (subNode.type == Type.ENTRY) {
val entry = subNode as EntryVersioned
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextSize)
}
mDatabase.stopManageEntry(entry)
}
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
setTextSize(textSizeUnit, numberChildrenTextSize)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
@@ -361,8 +367,8 @@ class NodeAdapter
* Callback listener to redefine to do an action when a node is click * Callback listener to redefine to do an action when a node is click
*/ */
interface NodeClickCallback { interface NodeClickCallback {
fun onNodeClick(node: NodeVersioned) fun onNodeClick(node: Node)
fun onNodeLongClick(node: NodeVersioned): Boolean fun onNodeLongClick(node: Node): Boolean
} }
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.adapters package com.kunzisoft.keepass.adapters
@@ -22,26 +22,30 @@ package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.graphics.Color import android.graphics.Color
import androidx.cursoradapter.widget.CursorAdapter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.EntryCursor 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.EntryVersioned import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.PwIcon 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 java.util.* import com.kunzisoft.keepass.view.strikeOut
class SearchEntryCursorAdapter(context: Context, private val database: Database) class SearchEntryCursorAdapter(private val context: Context,
private val database: Database)
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) { : androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
private val cursorInflater: LayoutInflater = context.getSystemService( private val cursorInflater: LayoutInflater? = context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
private var displayUsername: Boolean = false private var displayUsername: Boolean = false
private val iconColor: Int private val iconColor: Int
@@ -60,7 +64,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
val view = cursorInflater.inflate(R.layout.item_search_entry, parent, false) val view = cursorInflater!!.inflate(R.layout.item_search_entry, parent, false)
val viewHolder = ViewHolder() val viewHolder = ViewHolder()
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon) viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon)
viewHolder.textViewTitle = view.findViewById(R.id.entry_text) viewHolder.textViewTitle = view.findViewById(R.id.entry_text)
@@ -71,56 +75,89 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
} }
override fun bindView(view: View, context: Context, cursor: Cursor) { override fun bindView(view: View, context: Context, cursor: Cursor) {
getEntryFrom(cursor)?.let { currentEntry ->
val viewHolder = view.tag as ViewHolder
// Retrieve elements from cursor // Assign image
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)), viewHolder.imageViewIcon?.assignDatabaseIcon(
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))) database.drawFactory,
val iconFactory = database.iconFactory currentEntry.icon,
var icon: PwIcon = iconFactory.getIcon( iconColor)
UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS)))) // Assign title
if (icon.isUnknown) { viewHolder.textViewTitle?.apply {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD))) text = currentEntry.getVisualTitle()
if (icon.isUnknown) strikeOut(currentEntry.isCurrentlyExpires)
icon = iconFactory.keyIcon }
// Assign subtitle
viewHolder.textViewSubTitle?.apply {
val entryUsername = currentEntry.username
text = if (displayUsername && entryUsername.isNotEmpty()) {
String.format("(%s)", entryUsername)
} else {
""
}
strikeOut(currentEntry.isCurrentlyExpires)
}
} }
val title = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE)) }
val username = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME))
val url = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL))
val viewHolder = view.tag as ViewHolder private fun getEntryFrom(cursor: Cursor): Entry? {
return database.createEntry()?.apply {
// Assign image database.startManageEntry(this)
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor) entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconFactory)
// Assign title }
val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString()) entryKDBX?.let { entryKDBX ->
viewHolder.textViewTitle?.text = showTitle (cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconFactory)
if (displayUsername && username.isNotEmpty()) { }
viewHolder.textViewSubTitle?.text = String.format("(%s)", username) database.stopManageEntry(this)
} else {
viewHolder.textViewSubTitle?.text = ""
} }
} }
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
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? {
var pwEntry: Entry? = null
val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) {
pwEntry = getEntryFrom(cursor)
}
return pwEntry
}
private class ViewHolder { private class ViewHolder {
internal var imageViewIcon: ImageView? = null internal var imageViewIcon: ImageView? = null
internal var textViewTitle: TextView? = null internal var textViewTitle: TextView? = null
internal var textViewSubTitle: TextView? = null internal var textViewSubTitle: TextView? = null
} }
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return database.searchEntries(constraint.toString())
}
fun getEntryFromPosition(position: Int): EntryVersioned? {
var pwEntry: EntryVersioned? = null
val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) {
pwEntry = database.getEntryFrom(cursor)
}
return pwEntry
}
} }

View File

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

View File

@@ -1,3 +1,22 @@
/*
* 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.app.database package com.kunzisoft.keepass.app.database
import android.os.AsyncTask import android.os.AsyncTask

View File

@@ -1,3 +1,22 @@
/*
* 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.app.database package com.kunzisoft.keepass.app.database
import androidx.room.Database import androidx.room.Database

View File

@@ -1,3 +1,22 @@
/*
* 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.app.database package com.kunzisoft.keepass.app.database
import android.content.Context import android.content.Context

View File

@@ -1,3 +1,22 @@
/*
* 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.app.database package com.kunzisoft.keepass.app.database
import androidx.room.* import androidx.room.*

View File

@@ -1,3 +1,22 @@
/*
* 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.app.database package com.kunzisoft.keepass.app.database
import android.os.Parcel import android.os.Parcel

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
@@ -112,6 +112,14 @@ class FileDatabaseHistoryAction(applicationContext: Context) {
).execute() ).execute()
} }
fun deleteKeyFileByDatabaseUri(databaseUri: Uri) {
ActionDatabaseAsyncTask(
{
databaseFileHistoryDao.deleteKeyFileByDatabaseUri(databaseUri.toString())
}
).execute()
}
fun deleteAllKeyFiles() { fun deleteAllKeyFiles() {
ActionDatabaseAsyncTask( ActionDatabaseAsyncTask(
{ {

View File

@@ -1,3 +1,22 @@
/*
* 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.app.database package com.kunzisoft.keepass.app.database
import androidx.room.* import androidx.room.*
@@ -19,6 +38,9 @@ interface FileDatabaseHistoryDao {
@Delete @Delete
fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int
@Query("UPDATE file_database_history SET keyfile_uri=null WHERE database_uri = :databaseUriString")
fun deleteKeyFileByDatabaseUri(databaseUriString: String)
@Query("UPDATE file_database_history SET keyfile_uri=null") @Query("UPDATE file_database_history SET keyfile_uri=null")
fun deleteAllKeyFiles() fun deleteAllKeyFiles()

View File

@@ -1,3 +1,22 @@
/*
* 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.app.database package com.kunzisoft.keepass.app.database
import androidx.room.ColumnInfo import androidx.room.ColumnInfo

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
@@ -26,15 +26,22 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.service.autofill.Dataset import android.service.autofill.Dataset
import android.service.autofill.FillResponse import android.service.autofill.FillResponse
import androidx.annotation.RequiresApi
import android.util.Log import android.util.Log
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue import android.view.autofill.AutofillValue
import android.widget.RemoteViews import android.widget.RemoteViews
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 java.util.* 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)
@@ -43,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 {
@@ -56,27 +64,44 @@ object AutofillHelper {
return String.format("%s (%s)", entryInfo.title, entryInfo.username) return String.format("%s (%s)", entryInfo.title, entryInfo.username)
if (entryInfo.title.isNotEmpty()) if (entryInfo.title.isNotEmpty())
return entryInfo.title return entryInfo.title
if (entryInfo.username.isNotEmpty())
return entryInfo.username
if (entryInfo.url.isNotEmpty()) if (entryInfo.url.isNotEmpty())
return entryInfo.url return entryInfo.url
if (entryInfo.username.isNotEmpty())
return entryInfo.username
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)
struct.password.forEach { id -> builder.setValue(id, AutofillValue.forText(entryInfo.password)) } struct.usernameId?.let { usernameId ->
builder.setValue(usernameId, AutofillValue.forText(entryInfo.username))
val ids = ArrayList(struct.username) }
if (entryInfo.username.contains("@") || struct.username.isEmpty()) struct.passwordId?.let { password ->
ids.addAll(struct.email) builder.setValue(password, AutofillValue.forText(entryInfo.password))
ids.forEach { id -> builder.setValue(id, AutofillValue.forText(entryInfo.username)) } }
return try { return try {
builder.build() builder.build()
@@ -87,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)) {
@@ -97,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(
@@ -116,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)
} }
@@ -140,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

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
@@ -31,8 +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.settings.PreferencesUtil import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.timeout.TimeoutHelper
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() { class AutofillLauncherActivity : AppCompatActivity() {
@@ -41,11 +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, assistStructure, PreferencesUtil.enableReadOnlyDatabase(this)) val searchInfo = SearchInfo().apply {
else { applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
FileDatabaseSelectActivity.launchForAutofillResult(this, assistStructure) webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
} }
// 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()
@@ -61,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

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
@@ -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.e(TAG, "Cancel autofill not implemented in this sample.") } 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 ->
if (listOf(*autofillIds).isNotEmpty()) { 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 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

@@ -1,27 +1,27 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi
import android.text.InputType import android.text.InputType
import androidx.annotation.RequiresApi
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.autofill.AutofillId import android.view.autofill.AutofillId
@@ -37,72 +37,196 @@ internal class StructureParser(private val structure: AssistStructure) {
private var usernameCandidate: AutofillId? = null private var usernameCandidate: AutofillId? = null
fun parse(): Result? { fun parse(): Result? {
result = Result() try {
result?.apply { result = Result()
usernameCandidate = null result?.apply {
for (i in 0 until structure.windowNodeCount) { usernameCandidate = null
val windowNode = structure.getWindowNodeAt(i) mainLoop@ for (i in 0 until structure.windowNodeCount) {
title.add(windowNode.title) val windowNode = structure.getWindowNodeAt(i)
windowNode.rootViewNode.webDomain?.let { applicationId = windowNode.title.toString().split("/")[0]
webDomain.add(it) Log.d(TAG, "Autofill applicationId: $applicationId")
}
parseViewNode(windowNode.rootViewNode)
}
// If not explicit username field found, add the field just before password field.
if (username.isEmpty() && email.isEmpty()
&& password.isNotEmpty() && usernameCandidate != null)
username.add(usernameCandidate!!)
}
return result 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
return if (result?.usernameId != null
&& result?.passwordId != null)
result
else
null
} catch (e: Exception) {
return null
}
} }
private fun parseViewNode(node: AssistStructure.ViewNode) { private fun parseViewNode(node: AssistStructure.ViewNode): Boolean {
val hints = node.autofillHints // Get the domain of a web app
node.webDomain?.let {
result?.domain = it
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
else if (parseNodeByAndroidInput(node))
return true
}
// Recursive method to process each node
for (i in 0 until node.childCount) {
if (parseViewNode(node.getChildAt(i)))
return true
}
}
return false
}
private fun parseNodeByAutofillHint(node: AssistStructure.ViewNode): Boolean {
val autofillId = node.autofillId val autofillId = node.autofillId
if (autofillId != null) { node.autofillHints?.forEach {
if (hints != null && hints.isNotEmpty()) { when {
when { it.equals(View.AUTOFILL_HINT_USERNAME, true)
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_USERNAME == it } -> result?.username?.add(autofillId) || it.equals(View.AUTOFILL_HINT_EMAIL_ADDRESS, true)
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_EMAIL_ADDRESS == it } -> result?.email?.add(autofillId) || it.equals(View.AUTOFILL_HINT_PHONE, true)
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_PASSWORD == it } -> result?.password?.add(autofillId) || it.equals("usernameOrEmail", true)-> {
else -> Log.d(TAG, "unsupported hints") result?.usernameId = autofillId
Log.d(TAG, "Autofill username hint")
} }
} else if (node.autofillType == View.AUTOFILL_TYPE_TEXT) { it.equals(View.AUTOFILL_HINT_PASSWORD, true)
val inputType = node.inputType || it.contains("password", true) -> {
when { result?.passwordId = autofillId
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS > 0 -> result?.email?.add(autofillId) Log.d(TAG, "Autofill password hint")
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD > 0 -> result?.password?.add(autofillId) return true
result?.password?.isEmpty() == true -> usernameCandidate = autofillId }
// Ignore autocomplete="off"
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
it.equals("off", true) ||
it.equals("on", true) -> {
Log.d(TAG, "Autofill web hint")
return parseNodeByHtmlAttributes(node)
}
else -> Log.d(TAG, "Autofill unsupported hint $it")
}
}
return false
}
private fun parseNodeByHtmlAttributes(node: AssistStructure.ViewNode): Boolean {
val autofillId = node.autofillId
val nodHtml = node.htmlInfo
when (nodHtml?.tag?.toLowerCase(Locale.ENGLISH)) {
"input" -> {
nodHtml.attributes?.forEach { pairAttribute ->
when (pairAttribute.first.toLowerCase(Locale.ENGLISH)) {
"type" -> {
when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) {
"tel", "email" -> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
}
"text" -> {
usernameCandidate = autofillId
Log.d(TAG, "Autofill username candidate web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
}
"password" -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
return true
}
}
}
}
} }
} }
} }
return false
}
for (i in 0 until node.childCount) private fun parseNodeByAndroidInput(node: AssistStructure.ViewNode): Boolean {
parseViewNode(node.getChildAt(i)) 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 {
val title: MutableList<CharSequence> var applicationId: String? = null
val webDomain: MutableList<String> var domain: String? = null
val username: MutableList<AutofillId> set(value) {
val email: MutableList<AutofillId> if (field == null)
val password: MutableList<AutofillId> field = value
}
init { var usernameId: AutofillId? = null
title = ArrayList() set(value) {
webDomain = ArrayList() if (field == null)
username = ArrayList() field = value
email = ArrayList() }
password = ArrayList()
} var passwordId: AutofillId? = null
set(value) {
if (field == null)
field = value
}
fun allAutofillIds(): Array<AutofillId> { fun allAutofillIds(): Array<AutofillId> {
val all = ArrayList<AutofillId>() val all = ArrayList<AutofillId>()
all.addAll(username) usernameId?.let {
all.addAll(email) all.add(it)
all.addAll(password) }
passwordId?.let {
all.add(it)
}
return all.toTypedArray() return all.toTypedArray()
} }
} }

View File

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

View File

@@ -1,13 +1,35 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric package com.kunzisoft.keepass.biometric
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.TextView import android.widget.TextView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.biometric.BiometricConstants
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@@ -19,12 +41,12 @@ import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
class AdvancedUnlockedManager(var context: FragmentActivity, class AdvancedUnlockedManager(var context: FragmentActivity,
var databaseFileUri: Uri, var databaseFileUri: Uri,
var advancedUnlockInfoView: AdvancedUnlockInfoView?, private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
var checkboxPasswordView: CompoundButton?, private var checkboxPasswordView: CompoundButton?,
var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null, private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
var passwordView: TextView?, var passwordView: TextView?,
var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit, private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit) private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback { : BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
@@ -34,65 +56,59 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext) private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
// fingerprint related code here init {
fun initBiometric() {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper == null || !biometricUnlockDatabaseHelper!!.isBiometricInitialized) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context, this)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.setAuthenticationCallback(biometricCallback)
}
// Add a check listener to change fingerprint mode // Add a check listener to change fingerprint mode
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked -> checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
checkBiometricAvailability() checkBiometricAvailability()
// Add old listener to enable the button, only be call here because of onCheckedChange bug // Add old listener to enable the button, only be call here because of onCheckedChange bug
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked) onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
} }
checkBiometricAvailability()
} }
@Synchronized /**
* Check biometric availability and change the current mode depending of device's state
*/
fun checkBiometricAvailability() { fun checkBiometricAvailability() {
// fingerprint not supported (by API level or hardware) so keep option hidden // biometric not supported (by API level or hardware) so keep option hidden
// or manually disable // or manually disable
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate() val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate()
if (!PreferencesUtil.isBiometricUnlockEnable(context) if (!PreferencesUtil.isBiometricUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) { || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.UNAVAILABLE) toggleMode(Mode.UNAVAILABLE)
} else { } else {
// biometric is available but not configured, show icon but in disabled state with some information
// fingerprint is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
toggleMode(Mode.NOT_CONFIGURED)
} else { } else {
if (checkboxPasswordView?.isChecked == true) { // Check if fingerprint well init (be called the first time the fingerprint is configured
// listen for encryption // and the activity still active)
toggleMode(Mode.STORE) if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
}
// Recheck to change the mode
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else { } else {
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
// fingerprint available but no stored password found yet for this DB so show info don't listen toggleMode(Mode.STORE_CREDENTIAL)
toggleMode( if (it) { } else {
// listen for decryption cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
Mode.OPEN // biometric available but no stored password found yet for this DB so show info don't listen
} else { toggleMode( if (containsCipher) {
// wait for typing // listen for decryption
Mode.WAIT_CREDENTIAL Mode.EXTRACT_CREDENTIAL
}) } else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
} }
} }
} }
@@ -106,7 +122,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
} }
} }
private val biometricCallback = object : BiometricPrompt.AuthenticationCallback () { private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
override fun onAuthenticationError( override fun onAuthenticationError(
errorCode: Int, errorCode: Int,
@@ -127,19 +143,15 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
context.runOnUiThread { context.runOnUiThread {
when (biometricMode) { when (biometricMode) {
Mode.UNAVAILABLE -> { Mode.UNAVAILABLE -> {}
} Mode.BIOMETRIC_NOT_CONFIGURED -> {}
Mode.PAUSE -> { Mode.KEY_MANAGER_UNAVAILABLE -> {}
} Mode.WAIT_CREDENTIAL -> {}
Mode.NOT_CONFIGURED -> { Mode.STORE_CREDENTIAL -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE -> {
// newly store the entered password in encrypted way // newly store the entered password in encrypted way
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString()) biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
} }
Mode.OPEN -> { Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences // retrieve the encrypted value from preferences
cipherDatabaseAction.getCipherDatabase(databaseFileUri) { cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
it?.encryptedValue?.let { value -> it?.encryptedValue?.let { value ->
@@ -155,11 +167,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private fun initNotAvailable() { private fun initNotAvailable() {
showFingerPrintViews(false) showFingerPrintViews(false)
advancedUnlockInfoView?.setIconViewClickListener(null) advancedUnlockInfoView?.setIconViewClickListener(false, null)
}
private fun initPause() {
advancedUnlockInfoView?.setIconViewClickListener(null)
} }
private fun initNotConfigured() { private fun initNotConfigured() {
@@ -167,7 +175,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.configure_biometric) setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("") setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(null) advancedUnlockInfoView?.setIconViewClickListener(false) {
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
}
}
private fun initKeyManagerNotAvailable() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
}
} }
private fun initWaitData() { private fun initWaitData() {
@@ -175,7 +195,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.no_credentials_stored) setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("") setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(null) advancedUnlockInfoView?.setIconViewClickListener(false) {
biometricAuthenticationCallback.onAuthenticationError(
BiometricConstants.ERROR_UNABLE_TO_PROCESS
, context.getString(R.string.credential_before_click_biometric_button))
}
}
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject,
promptInfo: BiometricPrompt.PromptInfo) {
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, cryptoObject)
}
} }
private fun initEncryptData() { private fun initEncryptData() {
@@ -188,9 +220,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
cryptoObject?.let { crypto -> cryptoObject?.let { crypto ->
// Set listener to open the biometric dialog and save credential // Set listener to open the biometric dialog and save credential
advancedUnlockInfoView?.setIconViewClickListener { _ -> advancedUnlockInfoView?.setIconViewClickListener { _ ->
context.runOnUiThread { openBiometricPrompt(biometricPrompt, crypto, promptInfo)
biometricPrompt?.authenticate(promptInfo, crypto)
}
} }
} }
@@ -211,17 +241,13 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
cryptoObject?.let { crypto -> cryptoObject?.let { crypto ->
// Set listener to open the biometric dialog and check credential // Set listener to open the biometric dialog and check credential
advancedUnlockInfoView?.setIconViewClickListener { _ -> advancedUnlockInfoView?.setIconViewClickListener { _ ->
context.runOnUiThread { openBiometricPrompt(biometricPrompt, crypto, promptInfo)
biometricPrompt?.authenticate(promptInfo, crypto)
}
} }
// Auto open the biometric prompt // Auto open the biometric prompt
if (isBiometricPromptAutoOpenEnable) { if (isBiometricPromptAutoOpenEnable) {
isBiometricPromptAutoOpenEnable = false isBiometricPromptAutoOpenEnable = false
context.runOnUiThread { openBiometricPrompt(biometricPrompt, crypto, promptInfo)
biometricPrompt?.authenticate(promptInfo, crypto)
}
} }
} }
@@ -235,28 +261,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun initBiometricMode() { fun initBiometricMode() {
when (biometricMode) { when (biometricMode) {
Mode.UNAVAILABLE -> initNotAvailable() Mode.UNAVAILABLE -> initNotAvailable()
Mode.PAUSE -> initPause() Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.NOT_CONFIGURED -> initNotConfigured() Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.WAIT_CREDENTIAL -> initWaitData() Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE -> initEncryptData() Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.OPEN -> initDecryptData() Mode.EXTRACT_CREDENTIAL -> initDecryptData()
} }
// Show fingerprint key deletion // Show fingerprint key deletion
context.invalidateOptionsMenu() context.invalidateOptionsMenu()
} }
fun pause() {
biometricMode = Mode.PAUSE
initBiometricMode()
}
fun destroy() { fun destroy() {
// Restore the checked listener // Restore the checked listener
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener) checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
biometricMode = Mode.UNAVAILABLE
initBiometricMode()
biometricUnlockDatabaseHelper = null
} }
// Only to fix multiple fingerprint menu #332 // Only to fix multiple fingerprint menu #332
@@ -265,7 +282,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
if (!addBiometricMenuInProgress) { if (!addBiometricMenuInProgress) {
addBiometricMenuInProgress = true addBiometricMenuInProgress = true
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.NOT_CONFIGURED) if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.BIOMETRIC_NOT_CONFIGURED)
&& it) { && it) {
menuInflater.inflate(R.menu.advanced_unlock, menu) menuInflater.inflate(R.menu.advanced_unlock, menu)
addBiometricMenuInProgress = false addBiometricMenuInProgress = false
@@ -277,7 +294,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun deleteEntryKey() { fun deleteEntryKey() {
biometricUnlockDatabaseHelper?.deleteEntryKey() biometricUnlockDatabaseHelper?.deleteEntryKey()
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri) cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri)
biometricMode = Mode.NOT_CONFIGURED biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED
checkBiometricAvailability() checkBiometricAvailability()
} }
@@ -296,8 +313,9 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
} }
override fun onBiometricException(e: Exception) { override fun onBiometricException(e: Exception) {
if (e.localizedMessage != null) e.localizedMessage?.let {
setAdvancedUnlockedMessageView(e.localizedMessage) setAdvancedUnlockedMessageView(it)
}
} }
private fun showFingerPrintViews(show: Boolean) { private fun showFingerPrintViews(show: Boolean) {
@@ -323,7 +341,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
} }
enum class Mode { enum class Mode {
UNAVAILABLE, PAUSE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN UNAVAILABLE, BIOMETRIC_NOT_CONFIGURED, KEY_MANAGER_UNAVAILABLE, WAIT_CREDENTIAL, STORE_CREDENTIAL, EXTRACT_CREDENTIAL
} }
companion object { companion object {

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.biometric package com.kunzisoft.keepass.biometric
@@ -42,8 +42,7 @@ import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity, class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private val biometricUnlockCallback: BiometricUnlockCallback) {
private var biometricPrompt: BiometricPrompt? = null private var biometricPrompt: BiometricPrompt? = null
@@ -53,12 +52,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
private var keyguardManager: KeyguardManager? = null private var keyguardManager: KeyguardManager? = null
private var cryptoObject: BiometricPrompt.CryptoObject? = null private var cryptoObject: BiometricPrompt.CryptoObject? = null
private var isBiometricInit = false private var isKeyManagerInit = false
private var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
var biometricUnlockCallback: BiometricUnlockCallback? = null
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply { private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
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)
// TODO device credential // TODO device credential
/* /*
if (keyguardManager?.isDeviceSecure == true) if (keyguardManager?.isDeviceSecure == true)
@@ -70,7 +71,8 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply { private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
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)
// TODO device credential // TODO device credential
/* /*
if (keyguardManager?.isDeviceSecure == true) if (keyguardManager?.isDeviceSecure == true)
@@ -80,20 +82,20 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
setNegativeButtonText(context.getString(android.R.string.cancel)) setNegativeButtonText(context.getString(android.R.string.cancel))
}.build() }.build()
val isBiometricInitialized: Boolean val isKeyManagerInitialized: Boolean
get() { get() {
if (!isBiometricInit) { if (!isKeyManagerInit) {
biometricUnlockCallback.onBiometricException(Exception("Biometric not initialized")) biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
} }
return isBiometricInit return isKeyManagerInit
} }
init { init {
if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) { if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) {
// really not much to do when no fingerprint support found // really not much to do when no fingerprint support found
isBiometricInit = false isKeyManagerInit = false
} else { } else {
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
try { try {
this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE) this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE)
@@ -103,17 +105,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
+ BIOMETRIC_BLOCKS_MODES + "/" + BIOMETRIC_BLOCKS_MODES + "/"
+ BIOMETRIC_ENCRYPTION_PADDING) + BIOMETRIC_ENCRYPTION_PADDING)
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!) this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
isBiometricInit = true isKeyManagerInit = (keyStore != null
&& keyGenerator != null
&& cipher != null)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to initialize the keystore", e) Log.e(TAG, "Unable to initialize the keystore", e)
isBiometricInit = false isKeyManagerInit = false
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
} }
} }
private fun getSecretKey(): SecretKey? { private fun getSecretKey(): SecretKey? {
if (!isBiometricInitialized) { if (!isKeyManagerInitialized) {
return null return null
} }
try { try {
@@ -139,14 +143,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to create a key in keystore", e) Log.e(TAG, "Unable to create a key in keystore", e)
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey? return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey?
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to retrieve the key in keystore", e) Log.e(TAG, "Unable to retrieve the key in keystore", e)
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
return null return null
} }
@@ -155,7 +159,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
: (biometricPrompt: BiometricPrompt?, : (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?, cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) { promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isBiometricInitialized) { if (!isKeyManagerInitialized) {
return return
} }
try { try {
@@ -168,19 +172,18 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
} catch (unrecoverableKeyException: UnrecoverableKeyException) { } catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException) Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
biometricUnlockCallback.onInvalidKeyException(unrecoverableKeyException) biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) { } catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException) Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
biometricUnlockCallback.onInvalidKeyException(invalidKeyException) biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to initialize encrypt data", e) Log.e(TAG, "Unable to initialize encrypt data", e)
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
} }
fun encryptData(value: String) { fun encryptData(value: String) {
if (!isBiometricInitialized) { if (!isKeyManagerInitialized) {
return return
} }
try { try {
@@ -190,21 +193,20 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
// passes updated iv spec on to callback so this can be stored for decryption // passes updated iv spec on to callback so this can be stored for decryption
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec -> cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP) val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
biometricUnlockCallback.handleEncryptedResult(encryptedBase64, ivSpecValue) biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to encrypt data", e) Log.e(TAG, "Unable to encrypt data", e)
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
} }
fun initDecryptData(ivSpecValue: String, actionIfCypherInit fun initDecryptData(ivSpecValue: String, actionIfCypherInit
: (biometricPrompt: BiometricPrompt?, : (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?, cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) { promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isBiometricInitialized) { if (!isKeyManagerInitialized) {
return return
} }
try { try {
@@ -224,32 +226,30 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
deleteEntryKey() deleteEntryKey()
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) { } catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException) Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
biometricUnlockCallback.onInvalidKeyException(invalidKeyException) biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to initialize decrypt data", e) Log.e(TAG, "Unable to initialize decrypt data", e)
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
} }
fun decryptData(encryptedValue: String) { fun decryptData(encryptedValue: String) {
if (!isBiometricInitialized) { if (!isKeyManagerInitialized) {
return return
} }
try { try {
// actual decryption here // actual decryption here
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP) val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
cipher?.doFinal(encrypted)?.let { decrypted -> cipher?.doFinal(encrypted)?.let { decrypted ->
biometricUnlockCallback.handleDecryptedResult(String(decrypted)) biometricUnlockCallback?.handleDecryptedResult(String(decrypted))
} }
} catch (badPaddingException: BadPaddingException) { } catch (badPaddingException: BadPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException) Log.e(TAG, "Unable to decrypt data", badPaddingException)
biometricUnlockCallback.onInvalidKeyException(badPaddingException) biometricUnlockCallback?.onInvalidKeyException(badPaddingException)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to decrypt data", e) Log.e(TAG, "Unable to decrypt data", e)
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
} }
fun deleteEntryKey() { fun deleteEntryKey() {
@@ -258,14 +258,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY) keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to delete entry key in keystore", e) Log.e(TAG, "Unable to delete entry key in keystore", e)
biometricUnlockCallback.onBiometricException(e) biometricUnlockCallback?.onBiometricException(e)
} }
} }
fun setAuthenticationCallback(authenticationCallback: BiometricPrompt.AuthenticationCallback) {
this.authenticationCallback = authenticationCallback
}
@Synchronized @Synchronized
fun initBiometricPrompt() { fun initBiometricPrompt() {
if (biometricPrompt == null) { if (biometricPrompt == null) {
@@ -299,22 +295,24 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
* Remove entry key in keystore * Remove entry key in keystore
*/ */
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity, fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
biometricUnlockCallback: BiometricUnlockErrorCallback) { biometricCallback: BiometricUnlockErrorCallback) {
val fingerPrintHelper = BiometricUnlockDatabaseHelper(context, object : BiometricUnlockCallback { BiometricUnlockDatabaseHelper(context).apply {
biometricUnlockCallback = object : BiometricUnlockCallback {
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {} override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleDecryptedResult(decryptedValue: String) {} override fun handleDecryptedResult(decryptedValue: String) {}
override fun onInvalidKeyException(e: Exception) { override fun onInvalidKeyException(e: Exception) {
biometricUnlockCallback.onInvalidKeyException(e) biometricCallback.onInvalidKeyException(e)
}
override fun onBiometricException(e: Exception) {
biometricCallback.onBiometricException(e)
}
} }
deleteEntryKey()
override fun onBiometricException(e: Exception) { }
biometricUnlockCallback.onBiometricException(e)
}
})
fingerPrintHelper.deleteEntryKey()
} }
} }

View File

@@ -1,62 +1,63 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.biometric package com.kunzisoft.keepass.biometric
import android.content.Context import android.content.Context
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.widget.ImageView import android.widget.ImageView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
@RequiresApi(api = Build.VERSION_CODES.M) @RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) { class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
private val scanFingerprint: AnimatedVectorDrawable = private val scanFingerprint: AnimatedVectorDrawableCompat? =
context.getDrawable(R.drawable.scan_fingerprint) as AnimatedVectorDrawable AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint)
init { init {
imageView.setImageDrawable(scanFingerprint) imageView.setImageDrawable(scanFingerprint)
} }
private var animationCallback = object : Animatable2.AnimationCallback() { private var animationCallback = object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable) { override fun onAnimationEnd(drawable: Drawable) {
if (!scanFingerprint.isRunning) imageView.post {
scanFingerprint.start() scanFingerprint?.start()
}
} }
} }
fun startScan() { fun startScan() {
scanFingerprint.registerAnimationCallback(animationCallback) scanFingerprint?.registerAnimationCallback(animationCallback)
if (!scanFingerprint.isRunning) if (scanFingerprint?.isRunning != true)
scanFingerprint.start() scanFingerprint?.start()
} }
fun stopScan() { fun stopScan() {
scanFingerprint.unregisterAnimationCallback(animationCallback) scanFingerprint?.unregisterAnimationCallback(animationCallback)
if (scanFingerprint.isRunning) if (scanFingerprint?.isRunning == true)
scanFingerprint.stop() scanFingerprint.stop()
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,33 +1,31 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto package com.kunzisoft.keepass.crypto
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.longTo8Bytes
import java.io.IOException import java.io.IOException
import java.security.DigestOutputStream import java.security.DigestOutputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.Arrays import java.util.*
import javax.crypto.Mac import javax.crypto.Mac
import kotlin.math.min import kotlin.math.min
@@ -60,7 +58,7 @@ object CryptoUtil {
throw RuntimeException(e) throw RuntimeException(e)
} }
val pbR = LEDataOutputStream.writeLongBuf(r) val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR) val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size) val copy = min(cbOut - pos, part.size)

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,28 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.engine package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
@@ -41,13 +41,28 @@ class AesEngine : CipherEngine() {
return cipher return cipher
} }
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm { override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return PwEncryptionAlgorithm.AESRijndael return EncryptionAlgorithm.AESRijndael
} }
companion object { companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0x31.toByte(), 0xC1.toByte(), 0xF2.toByte(), 0xE6.toByte(), 0xBF.toByte(), 0x71.toByte(), 0x43.toByte(), 0x50.toByte(), 0xBE.toByte(), 0x58.toByte(), 0x05.toByte(), 0x21.toByte(), 0x6A.toByte(), 0xFC.toByte(), 0x5A.toByte(), 0xFF.toByte())) byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
} }
} }

View File

@@ -1,26 +1,26 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.engine package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.spongycastle.jce.provider.BouncyCastleProvider import org.spongycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
@@ -44,13 +44,28 @@ class ChaCha20Engine : CipherEngine() {
return cipher return cipher
} }
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm { override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return PwEncryptionAlgorithm.ChaCha20 return EncryptionAlgorithm.ChaCha20
} }
companion object { companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xD6.toByte(), 0x03.toByte(), 0x8A.toByte(), 0x2B.toByte(), 0x8B.toByte(), 0x6F.toByte(), 0x4C.toByte(), 0xB5.toByte(), 0xA5.toByte(), 0x24.toByte(), 0x33.toByte(), 0x9A.toByte(), 0x31.toByte(), 0xDB.toByte(), 0xB5.toByte(), 0x9A.toByte())) byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
} }
} }

View File

@@ -1,25 +1,25 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.engine package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
@@ -46,6 +46,6 @@ abstract class CipherEngine {
return getCipher(opmode, key, IV, false) return getCipher(opmode, key, IV, false)
} }
abstract fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
} }

View File

@@ -1,33 +1,31 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.engine package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.UUID import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
@@ -47,13 +45,28 @@ class TwofishEngine : CipherEngine() {
return cipher return cipher
} }
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm { override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return PwEncryptionAlgorithm.Twofish return EncryptionAlgorithm.Twofish
} }
companion object { companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xAD.toByte(), 0x68.toByte(), 0xF2.toByte(), 0x9F.toByte(), 0x57.toByte(), 0x6F.toByte(), 0x4B.toByte(), 0xB9.toByte(), 0xA3.toByte(), 0x6A.toByte(), 0xD4.toByte(), 0x7A.toByte(), 0xF9.toByte(), 0x65.toByte(), 0x34.toByte(), 0x6C.toByte())) byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
} }
} }

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. 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;

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. 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;

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. 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;

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. 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;

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.crypto.keyDerivation
@@ -23,7 +23,7 @@ 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.FinalKeyFactory
import com.kunzisoft.keepass.utils.Types 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.*
@@ -32,7 +32,7 @@ class AesKdf internal constructor() : KdfEngine() {
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()) setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
} }
@@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() {
private const val DEFAULT_ROUNDS = 6000 private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = Types.bytestoUUID( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xC9.toByte(), byteArrayOf(0xC9.toByte(),
0xD9.toByte(), 0xD9.toByte(),
0xF3.toByte(), 0xF3.toByte(),

View File

@@ -1,27 +1,27 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation 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.utils.Types 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.*
@@ -30,7 +30,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters override val defaultParameters: KdfParameters
get() { get() {
val p = KdfParameters(uuid) val p = KdfParameters(uuid!!)
p.setParamUUID() p.setParamUUID()
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM) p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
@@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
companion object { companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xEF.toByte(), byteArrayOf(0xEF.toByte(),
0x63.toByte(), 0x63.toByte(),
0x6D.toByte(), 0x6D.toByte(),

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation; package com.kunzisoft.keepass.crypto.keyDerivation;

View File

@@ -1,25 +1,25 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.database.ObjectNameResource import com.kunzisoft.keepass.utils.ObjectNameResource
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable

View File

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

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation;
import com.kunzisoft.keepass.utils.VariantDictionary;
import com.kunzisoft.keepass.stream.LEDataInputStream;
import com.kunzisoft.keepass.stream.LEDataOutputStream;
import com.kunzisoft.keepass.utils.Types;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.UUID;
public class KdfParameters extends VariantDictionary {
private UUID kdfUUID;
private static final String ParamUUID = "$UUID";
KdfParameters(UUID uuid) {
kdfUUID = uuid;
}
public UUID getUUID() {
return kdfUUID;
}
protected void setParamUUID() {
setByteArray(ParamUUID, Types.UUIDtoBytes(kdfUUID));
}
public static KdfParameters deserialize(byte[] data) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
LEDataInputStream lis = new LEDataInputStream(bis);
VariantDictionary d = VariantDictionary.deserialize(lis);
if (d == null) {
return null;
}
UUID uuid = Types.bytestoUUID(d.getByteArray(ParamUUID));
KdfParameters kdfP = new KdfParameters(uuid);
kdfP.copyTo(d);
return kdfP;
}
public static byte[] serialize(KdfParameters kdf) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
LEDataOutputStream los = new LEDataOutputStream(bos);
KdfParameters.serialize(kdf, los);
return bos.toByteArray();
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.crypto.keyDerivation
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.stream.uuidTo16Bytes
import com.kunzisoft.keepass.utils.VariantDictionary
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.*
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() {
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
}
companion object {
private const val PARAM_UUID = "\$UUID"
@Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data)
val lis = LittleEndianDataInputStream(bis)
val d = deserialize(lis) ?: return null
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
val kdfP = KdfParameters(uuid)
kdfP.copyTo(d)
return kdfP
}
@Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream()
val los = LittleEndianDataOutputStream(bos)
serialize(kdf, los)
return bos.toByteArray()
}
}
}

View File

@@ -1,170 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.Type
import java.util.*
enum class SortNodeEnum {
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator<NodeVersioned> {
return when (this) {
DB -> NodeNaturalComparator(ascending, groupsBefore, false) // Force false because natural order contains recycle bin
TITLE -> NodeTitleComparator(ascending, groupsBefore, recycleBinBottom)
USERNAME -> NodeUsernameComparator(ascending, groupsBefore, recycleBinBottom)
CREATION_TIME -> NodeCreationComparator(ascending, groupsBefore, recycleBinBottom)
LAST_MODIFY_TIME -> NodeLastModificationComparator(ascending, groupsBefore, recycleBinBottom)
LAST_ACCESS_TIME -> NodeLastAccessComparator(ascending, groupsBefore, recycleBinBottom)
}
}
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<NodeVersioned> {
abstract fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int
private fun specificOrderOrHashIfEquals(object1: NodeVersioned, object2: NodeVersioned): Int {
val specificOrderComp = compareBySpecificOrder(object1, object2)
return if (specificOrderComp == 0) {
object1.hashCode() - object2.hashCode()
} else if (!ascending) -specificOrderComp else specificOrderComp // If descending, revert
}
override fun compare(object1: NodeVersioned,object2: NodeVersioned): Int {
if (object1 == object2)
return 0
if (object1.type == Type.GROUP) {
return if (object2.type == Type.GROUP) {
// RecycleBin at end of groups
if (recycleBinBottom) {
if (Database.getInstance().recycleBin == object1)
return 1
if (Database.getInstance().recycleBin == object2)
return -1
}
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.ENTRY) {
if (groupsBefore)
-1
else
1
} else {
-1
}
} else if (object1.type == Type.ENTRY) {
return if (object2.type == Type.ENTRY) {
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.GROUP) {
if (groupsBefore)
1
else
-1
} else {
-1
}
}
// Type not known
return -1
}
}
/**
* Comparator of node by natural database placement
*/
class NodeNaturalComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.nodePositionInParent.compareTo(object2.nodePositionInParent)
}
}
/**
* Comparator of Node by Title
*/
class NodeTitleComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.title.compareTo(object2.title, ignoreCase = true)
}
}
/**
* Comparator of Node by Username, Groups by title
*/
class NodeUsernameComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) {
// To get username if it's a ref
return (object1 as EntryVersioned).getEntryInfo(Database.getInstance()).username
.compareTo((object2 as EntryVersioned).getEntryInfo(Database.getInstance()).username,
ignoreCase = true)
}
return NodeTitleComparator(ascending, groupsBefore, recycleBinBottom).compare(object1, object2)
}
}
/**
* Comparator of node by creation
*/
class NodeCreationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.creationTime.date
.compareTo(object2.creationTime.date)
}
}
/**
* Comparator of node by last modification
*/
class NodeLastModificationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastModificationTime.date
.compareTo(object2.lastModificationTime.date)
}
}
/**
* Comparator of node by last access
*/
class NodeLastAccessComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastAccessTime.date
.compareTo(object2.lastAccessTime.date)
}
}
}

View File

@@ -1,43 +1,43 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor( open class AssignPasswordInDatabaseRunnable (
context: Context, context: Context,
database: Database, database: Database,
protected val mDatabaseUri: Uri,
withMasterPassword: Boolean, withMasterPassword: Boolean,
masterPassword: String?, masterPassword: String?,
withKeyFile: Boolean, withKeyFile: Boolean,
keyFile: Uri?, keyFile: Uri?)
save: Boolean, : SaveDatabaseRunnable(context, database, true) {
actionRunnable: ActionRunnable? = null)
: SaveDatabaseRunnable(context, database, save, actionRunnable) {
private var mMasterPassword: String? = null private var mMasterPassword: String? = null
private var mKeyFile: Uri? = null protected var mKeyFile: Uri? = null
private var mBackupKey: ByteArray? = null private var mBackupKey: ByteArray? = null
@@ -48,7 +48,7 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
this.mKeyFile = keyFile this.mKeyFile = keyFile
} }
override fun run() { override fun onStartRun() {
// Set key // Set key
try { try {
// TODO move master key methods // TODO move master key methods
@@ -57,17 +57,24 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFile) val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFile)
database.retrieveMasterKey(mMasterPassword, uriInputStream) database.retrieveMasterKey(mMasterPassword, uriInputStream)
// To save the database
super.run()
finishRun(true)
} catch (e: Exception) { } catch (e: Exception) {
erase(mBackupKey) erase(mBackupKey)
finishRun(false, e.message) setError(e)
} }
super.onStartRun()
} }
override fun onFinishRun(result: Result) { override fun onFinishRun() {
super.onFinishRun()
// Erase the biometric
CipherDatabaseAction.getInstance(context)
.deleteByDatabaseUri(mDatabaseUri)
// Erase the register keyfile
FileDatabaseHistoryAction.getInstance(context)
.deleteKeyFileByDatabaseUri(mDatabaseUri)
if (!result.isSuccess) { if (!result.isSuccess) {
// Erase the current master key // Erase the current master key
erase(database.masterKey) erase(database.masterKey)
@@ -75,8 +82,6 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
database.masterKey = it database.masterKey = it
} }
} }
super.onFinishRun(result)
} }
/** /**

View File

@@ -1,58 +1,70 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.settings.PreferencesUtil
class CreateDatabaseRunnable(context: Context, class CreateDatabaseRunnable(context: Context,
private val mDatabaseUri: Uri,
private val mDatabase: Database, private val mDatabase: Database,
databaseUri: Uri,
private val databaseName: String,
private val rootName: String,
withMasterPassword: Boolean, withMasterPassword: Boolean,
masterPassword: String?, masterPassword: String?,
withKeyFile: Boolean, withKeyFile: Boolean,
keyFile: Uri?, keyFile: Uri?)
save: Boolean, : AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) {
actionRunnable: ActionRunnable? = null)
: AssignPasswordInDatabaseRunnable(context, mDatabase, withMasterPassword, masterPassword, withKeyFile, keyFile, save, actionRunnable) {
override fun run() { override fun onStartRun() {
try { try {
// Create new database record // Create new database record
mDatabase.apply { mDatabase.apply {
createData(mDatabaseUri) createData(mDatabaseUri, databaseName, rootName)
// Set Database state // Set Database state
loaded = true loaded = true
// Commit changes
super.run()
} }
finishRun(true)
} catch (e: Exception) { } catch (e: Exception) {
mDatabase.closeAndClear() mDatabase.closeAndClear()
finishRun(false, e.message) setError(e)
} }
super.onStartRun()
} }
override fun onFinishRun(result: Result) {} override fun onFinishRun() {
super.onFinishRun()
if (result.isSuccess) {
// Add database to recent files
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context.applicationContext)
.addOrUpdateDatabaseUri(mDatabaseUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mKeyFile else null)
}
} else {
Log.e("CreateDatabaseRunnable", "Unable to create the database")
}
}
} }

View File

@@ -1,20 +1,20 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
@@ -25,7 +25,9 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
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.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
@@ -40,16 +42,18 @@ class LoadDatabaseRunnable(private val context: Context,
private val mOmitBackup: Boolean, private val mOmitBackup: Boolean,
private val mFixDuplicateUUID: Boolean, private val mFixDuplicateUUID: Boolean,
private val progressTaskUpdater: ProgressTaskUpdater?, private val progressTaskUpdater: ProgressTaskUpdater?,
actionFinishRunnable: ActionRunnable?) private val mDuplicateUuidAction: ((Result) -> Unit)?)
: ActionRunnable(actionFinishRunnable, executeNestedActionIfResultFalse = true) { : ActionRunnable() {
private val cacheDirectory = context.applicationContext.filesDir private val cacheDirectory = context.applicationContext.filesDir
override fun run() { override fun onStartRun() {
try { // Clear before we load
// Clear before we load mDatabase.closeAndClear(cacheDirectory)
mDatabase.closeAndClear(cacheDirectory) }
override fun onActionRun() {
try {
mDatabase.loadData(mUri, mPass, mKey, mDatabase.loadData(mUri, mPass, mKey,
mReadonly, mReadonly,
context.contentResolver, context.contentResolver,
@@ -57,35 +61,37 @@ class LoadDatabaseRunnable(private val context: Context,
mOmitBackup, mOmitBackup,
mFixDuplicateUUID, mFixDuplicateUUID,
progressTaskUpdater) progressTaskUpdater)
}
catch (e: DuplicateUuidDatabaseException) {
mDuplicateUuidAction?.invoke(result)
setError(e)
}
catch (e: LoadDatabaseException) {
setError(e)
}
}
override fun onFinishRun() {
if (result.isSuccess) {
// Save keyFile in app database // Save keyFile in app database
val rememberKeyFile = PreferencesUtil.rememberKeyFiles(context) if (PreferencesUtil.rememberDatabaseLocations(context)) {
if (rememberKeyFile) {
var keyUri = mKey
if (!rememberKeyFile) {
keyUri = null
}
FileDatabaseHistoryAction.getInstance(context) FileDatabaseHistoryAction.getInstance(context)
.addOrUpdateDatabaseUri(mUri, keyUri) .addOrUpdateDatabaseUri(mUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mKey else null)
} }
// Register the biometric // Register the biometric
mCipherEntity?.let { cipherDatabaseEntity -> mCipherEntity?.let { cipherDatabaseEntity ->
CipherDatabaseAction.getInstance(context) CipherDatabaseAction.getInstance(context)
.addOrUpdateCipherDatabase(cipherDatabaseEntity) { .addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
finishRun(true)
}
} ?: run {
finishRun(true)
} }
}
catch (e: LoadDatabaseException) {
finishRun(false, e)
}
}
override fun onFinishRun(result: Result) { // Register the current time to init the lock timer
if (!result.isSuccess) { PreferencesUtil.saveCurrentTime(context)
// Start the opening notification
DatabaseOpenNotificationService.start(context)
} else {
mDatabase.closeAndClear(cacheDirectory) mDatabase.closeAndClear(cacheDirectory)
} }
} }

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.* import android.content.*
@@ -8,30 +27,40 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.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.*
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
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.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COLOR_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COMPRESSION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DESCRIPTION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ENCRYPTION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ITERATIONS_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_NAME_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_PARALLELISM_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_PARALLELISM_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
@@ -44,10 +73,10 @@ import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class ProgressDialogThread(private val activity: FragmentActivity) {
class ProgressDialogThread(private val activity: FragmentActivity, var onActionFinish: ((actionTask: String,
var onActionFinish: (actionTask: String, result: ActionRunnable.Result) -> Unit)? = null
result: ActionRunnable.Result) -> Unit) {
private var intentDatabaseTask = Intent(activity, DatabaseTaskNotificationService::class.java) private var intentDatabaseTask = Intent(activity, DatabaseTaskNotificationService::class.java)
@@ -59,19 +88,35 @@ class ProgressDialogThread(private val activity: FragmentActivity,
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
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() 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) ProgressTaskDialogFragment.stop(activity)
TimeoutHelper.releaseTemporarilyDisableTimeoutAndLockIfTimeout(activity) TimeoutHelper.releaseTemporarilyDisableTimeout()
val inTime = if (activity is LockingActivity) {
TimeoutHelper.checkTimeAndLockIfTimeout(activity)
} else {
TimeoutHelper.checkTime(activity)
}
// Start the opening notification if in time
// (databaseOpenService is open manually in Action Open Task)
if (actionTask != ACTION_DATABASE_LOAD_TASK && inTime) {
DatabaseOpenNotificationService.start(activity)
}
} }
} }
@@ -99,7 +144,7 @@ class ProgressDialogThread(private val activity: FragmentActivity,
if (serviceConnection == null) { if (serviceConnection == null) {
serviceConnection = object : ServiceConnection { serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) { override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder).apply { mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addActionTaskListener(actionTaskListener) addActionTaskListener(actionTaskListener)
getService().checkAction() getService().checkAction()
} }
@@ -172,7 +217,11 @@ class ProgressDialogThread(private val activity: FragmentActivity,
unBindService() unBindService()
activity.unregisterReceiver(databaseTaskBroadcastReceiver) try {
activity.unregisterReceiver(databaseTaskBroadcastReceiver)
} catch (e: IllegalArgumentException) {
// If receiver not register, do nothing
}
} }
@Synchronized @Synchronized
@@ -228,12 +277,14 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, ACTION_DATABASE_LOAD_TASK) , ACTION_DATABASE_LOAD_TASK)
} }
fun startDatabaseAssignPassword(masterPasswordChecked: Boolean, fun startDatabaseAssignPassword(databaseUri: Uri,
masterPasswordChecked: Boolean,
masterPassword: String?, masterPassword: String?,
keyFileChecked: Boolean, keyFileChecked: Boolean,
keyFile: Uri?) { keyFile: Uri?) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked) putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword) putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked) putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked)
@@ -248,8 +299,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
---- ----
*/ */
fun startDatabaseCreateGroup(newGroup: GroupVersioned, fun startDatabaseCreateGroup(newGroup: Group,
parent: GroupVersioned, parent: Group,
save: Boolean) { save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.GROUP_KEY, newGroup) putParcelable(DatabaseTaskNotificationService.GROUP_KEY, newGroup)
@@ -259,8 +310,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, ACTION_DATABASE_CREATE_GROUP_TASK) , ACTION_DATABASE_CREATE_GROUP_TASK)
} }
fun startDatabaseUpdateGroup(oldGroup: GroupVersioned, fun startDatabaseUpdateGroup(oldGroup: Group,
groupToUpdate: GroupVersioned, groupToUpdate: Group,
save: Boolean) { save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.GROUP_ID_KEY, oldGroup.nodeId) putParcelable(DatabaseTaskNotificationService.GROUP_ID_KEY, oldGroup.nodeId)
@@ -270,8 +321,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, ACTION_DATABASE_UPDATE_GROUP_TASK) , ACTION_DATABASE_UPDATE_GROUP_TASK)
} }
fun startDatabaseCreateEntry(newEntry: EntryVersioned, fun startDatabaseCreateEntry(newEntry: Entry,
parent: GroupVersioned, parent: Group,
save: Boolean) { save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, newEntry) putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, newEntry)
@@ -281,8 +332,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, ACTION_DATABASE_CREATE_ENTRY_TASK) , ACTION_DATABASE_CREATE_ENTRY_TASK)
} }
fun startDatabaseUpdateEntry(oldEntry: EntryVersioned, fun startDatabaseUpdateEntry(oldEntry: Entry,
entryToUpdate: EntryVersioned, entryToUpdate: Entry,
save: Boolean) { save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, oldEntry.nodeId) putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, oldEntry.nodeId)
@@ -293,20 +344,20 @@ class ProgressDialogThread(private val activity: FragmentActivity,
} }
private fun startDatabaseActionListNodes(actionTask: String, private fun startDatabaseActionListNodes(actionTask: String,
nodesPaste: List<NodeVersioned>, nodesPaste: List<Node>,
newParent: GroupVersioned?, newParent: Group?,
save: Boolean) { save: Boolean) {
val groupsIdToCopy = ArrayList<PwNodeId<*>>() val groupsIdToCopy = ArrayList<NodeId<*>>()
val entriesIdToCopy = ArrayList<PwNodeId<UUID>>() val entriesIdToCopy = ArrayList<NodeId<UUID>>()
nodesPaste.forEach { nodeVersioned -> nodesPaste.forEach { nodeVersioned ->
when (nodeVersioned.type) { when (nodeVersioned.type) {
Type.GROUP -> { Type.GROUP -> {
(nodeVersioned as GroupVersioned).nodeId?.let { groupId -> (nodeVersioned as Group).nodeId?.let { groupId ->
groupsIdToCopy.add(groupId) groupsIdToCopy.add(groupId)
} }
} }
Type.ENTRY -> { Type.ENTRY -> {
entriesIdToCopy.add((nodeVersioned as EntryVersioned).nodeId) entriesIdToCopy.add((nodeVersioned as Entry).nodeId)
} }
} }
} }
@@ -323,23 +374,51 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, actionTask) , actionTask)
} }
fun startDatabaseCopyNodes(nodesToCopy: List<NodeVersioned>, fun startDatabaseCopyNodes(nodesToCopy: List<Node>,
newParent: GroupVersioned, newParent: Group,
save: Boolean) { save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_COPY_NODES_TASK, nodesToCopy, newParent, save) startDatabaseActionListNodes(ACTION_DATABASE_COPY_NODES_TASK, nodesToCopy, newParent, save)
} }
fun startDatabaseMoveNodes(nodesToMove: List<NodeVersioned>, fun startDatabaseMoveNodes(nodesToMove: List<Node>,
newParent: GroupVersioned, newParent: Group,
save: Boolean) { save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_MOVE_NODES_TASK, nodesToMove, newParent, save) startDatabaseActionListNodes(ACTION_DATABASE_MOVE_NODES_TASK, nodesToMove, newParent, save)
} }
fun startDatabaseDeleteNodes(nodesToDelete: List<NodeVersioned>, fun startDatabaseDeleteNodes(nodesToDelete: List<Node>,
save: Boolean) { save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save) startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save)
} }
/*
-----------------
Entry History Settings
-----------------
*/
fun startDatabaseRestoreEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
}
fun startDatabaseDeleteEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_DELETE_ENTRY_HISTORY)
}
/* /*
----------------- -----------------
Main Settings Main Settings
@@ -347,66 +426,80 @@ class ProgressDialogThread(private val activity: FragmentActivity,
*/ */
fun startDatabaseSaveName(oldName: String, fun startDatabaseSaveName(oldName: String,
newName: String) { newName: String,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldName) putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldName)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_NAME_TASK) , ACTION_DATABASE_UPDATE_NAME_TASK)
} }
fun startDatabaseSaveDescription(oldDescription: String, fun startDatabaseSaveDescription(oldDescription: String,
newDescription: String) { newDescription: String,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDescription) putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDescription)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_DESCRIPTION_TASK) , ACTION_DATABASE_UPDATE_DESCRIPTION_TASK)
} }
fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String, fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String,
newDefaultUsername: String) { newDefaultUsername: String,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDefaultUsername) putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDefaultUsername)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK) , ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK)
} }
fun startDatabaseSaveColor(oldColor: String, fun startDatabaseSaveColor(oldColor: String,
newColor: String) { newColor: String,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldColor) putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldColor)
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_COLOR_TASK) , ACTION_DATABASE_UPDATE_COLOR_TASK)
} }
fun startDatabaseSaveCompression(oldCompression: PwCompressionAlgorithm, fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm,
newCompression: PwCompressionAlgorithm) { newCompression: CompressionAlgorithm,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldCompression) putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldCompression)
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_COMPRESSION_TASK) , ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
} }
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int, fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
newMaxHistoryItems: Int) { newMaxHistoryItems: Int,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistoryItems) putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistoryItems)
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems) putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK) , ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK)
} }
fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long, fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long,
newMaxHistorySize: Long) { newMaxHistorySize: Long,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistorySize) putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistorySize)
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK) , ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK)
} }
/* /*
@@ -415,48 +508,68 @@ class ProgressDialogThread(private val activity: FragmentActivity,
------------------- -------------------
*/ */
fun startDatabaseSaveEncryption(oldEncryption: PwEncryptionAlgorithm, fun startDatabaseSaveEncryption(oldEncryption: EncryptionAlgorithm,
newEncryption: PwEncryptionAlgorithm) { newEncryption: EncryptionAlgorithm,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldEncryption) putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldEncryption)
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_ENCRYPTION_TASK) , ACTION_DATABASE_UPDATE_ENCRYPTION_TASK)
} }
fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine, fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine,
newKeyDerivation: KdfEngine) { newKeyDerivation: KdfEngine,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldKeyDerivation) putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldKeyDerivation)
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK) , ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK)
} }
fun startDatabaseSaveIterations(oldIterations: Long, fun startDatabaseSaveIterations(oldIterations: Long,
newIterations: Long) { newIterations: Long,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldIterations) putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldIterations)
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_ITERATIONS_TASK) , ACTION_DATABASE_UPDATE_ITERATIONS_TASK)
} }
fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long, fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long,
newMemoryUsage: Long) { newMemoryUsage: Long,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMemoryUsage) putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMemoryUsage)
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK) , ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
} }
fun startDatabaseSaveParallelism(oldParallelism: Int, fun startDatabaseSaveParallelism(oldParallelism: Int,
newParallelism: Int) { newParallelism: Int,
save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism) putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism) putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_SAVE_PARALLELISM_TASK) , ACTION_DATABASE_UPDATE_PARALLELISM_TASK)
}
/**
* Save Database without parameter
*/
fun startDatabaseSave(save: Boolean) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_SAVE)
} }
} }

View File

@@ -1,63 +1,50 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePassDX.
* *
* KeePass DX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* KeePass DX is distributed in the hope that it will be useful, * KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import java.io.IOException
abstract class SaveDatabaseRunnable(protected var context: Context, open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database, protected var database: Database,
protected var saveDatabase: Boolean, private var saveDatabase: Boolean)
nestedAction: ActionRunnable? = null) : ActionRunnable() {
: ActionRunnable(nestedAction) {
override fun run() { var mAfterSaveDatabase: ((Result) -> Unit)? = null
if (saveDatabase) {
override fun onStartRun() {}
override fun onActionRun() {
if (saveDatabase && result.isSuccess) {
try { try {
database.saveData(context.contentResolver) database.saveData(context.contentResolver)
} catch (e: IOException) { } catch (e: DatabaseException) {
finishRun(false, e.message) setError(e)
} catch (e: DatabaseOutputException) {
finishRun(false, e.message)
} }
} }
// Need to call super.run() in child class
} }
override fun onFinishRun(result: Result) { override fun onFinishRun() {
// Need to call super.onFinishRun(result) in child class // Need to call super.onFinishRun() in child class
} mAfterSaveDatabase?.invoke(result)
}
class SaveDatabaseActionRunnable(context: Context,
database: Database,
save: Boolean,
nestedAction: ActionRunnable? = null)
: SaveDatabaseRunnable(context, database, save, nestedAction) {
override fun run() {
super.run()
finishRun(true)
} }
} }

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