Compare commits

...

193 Commits

Author SHA1 Message Date
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
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
458 changed files with 14818 additions and 10240 deletions

View File

@@ -38,7 +38,7 @@ If applicable, add screenshots to help explain your problem.
**Android (please complete the following information):**
- Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1]
- Browser: [e.g. Chrome]
**Additional context**
Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

View File

@@ -1,3 +1,24 @@
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)

View File

@@ -11,7 +11,7 @@
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
* Allows **fast copy** of fields and opening of URI / URL
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
* **One-Time Password** management *(HOTP / TOTP)*
* **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
* Material design with **themes**
* **AutoFill** and Integration
* Field filling **keyboard**
@@ -65,7 +65,7 @@ Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassD
## 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 KeePassDX.

View File

@@ -6,14 +6,13 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
ndkVersion "20.1.5948944"
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 28
versionCode = 24
versionName = "2.5.0.0beta24"
versionCode = 26
versionName = "2.5.0.0beta26"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -28,7 +27,6 @@ android {
}
}
buildTypes {
release {
minifyEnabled = false
@@ -109,8 +107,6 @@ dependencies {
implementation 'org.apache.commons:commons-io:1.3.2'
// Apache Commons Codec
implementation 'commons-codec:commons-codec:1.11'
// Base64
implementation 'biz.source_code:base64coder:2010-12-19'
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')

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,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -19,20 +19,15 @@
*/
package com.kunzisoft.keepass.tests
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.Calendar
import java.util.Random
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.*
import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.*
import com.kunzisoft.keepass.database.element.PwDate
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.Types
class TypesTest : TestCase() {
class StringDatabaseKDBUtilsTest : TestCase() {
fun testReadWriteLongZero() {
testReadWriteLong(0.toByte())
@@ -56,15 +51,9 @@ class TypesTest : TestCase() {
private fun testReadWriteLong(value: Byte) {
val orig = ByteArray(8)
val dest = ByteArray(8)
setArray(orig, value, 0, 8)
val one = LEDataInputStream.readLong(orig, 0)
LEDataOutputStream.writeLong(one, dest, 0)
assertArrayEquals(orig, dest)
setArray(orig, value, 8)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
}
fun testReadWriteIntZero() {
@@ -81,24 +70,22 @@ class TypesTest : TestCase() {
private fun testReadWriteInt(value: Byte) {
val orig = ByteArray(4)
val dest = ByteArray(4)
for (i in 0..3) {
orig[i] = 0
}
setArray(orig, value, 0, 4)
setArray(orig, value, 4)
val one = LEDataInputStream.readInt(orig, 0)
LEDataOutputStream.writeInt(one, dest, 0)
val one = bytes4ToInt(orig)
val dest = intTo4Bytes(one)
assertArrayEquals(orig, dest)
}
private fun setArray(buf: ByteArray, value: Byte, offset: Int, size: Int) {
for (i in offset until offset + size) {
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
for (i in 0 until size) {
buf[i] = value
}
}
@@ -109,11 +96,10 @@ class TypesTest : TestCase() {
orig[0] = 0
orig[1] = 1
val one = LEDataInputStream.readUShort(orig, 0)
val dest = LEDataOutputStream.writeUShortBuf(one)
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest)
}
fun testReadWriteShortMin() {
@@ -126,15 +112,12 @@ class TypesTest : TestCase() {
private fun testReadWriteShort(value: Byte) {
val orig = ByteArray(2)
val dest = ByteArray(2)
setArray(orig, value, 2)
setArray(orig, value, 0, 2)
val one = LEDataInputStream.readUShort(orig, 0)
LEDataOutputStream.writeUShort(one, dest, 0)
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest)
}
fun testReadWriteByteZero() {
@@ -150,16 +133,8 @@ class TypesTest : TestCase() {
}
private fun testReadWriteByte(value: Byte) {
val orig = ByteArray(1)
val dest = ByteArray(1)
setArray(orig, value, 0, 1)
val one = Types.readUByte(orig, 0)
Types.writeUByte(one, dest, 0)
assertArrayEquals(orig, dest)
val dest: Byte = uIntToByte(byteToUInt(value))
assert(value == dest)
}
fun testDate() {
@@ -168,27 +143,37 @@ class TypesTest : TestCase() {
val expected = Calendar.getInstance()
expected.set(2008, 1, 2, 3, 4, 5)
val buf = PwDate.writeTime(expected.time, cal)
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("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("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
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() {
val rnd = Random()
val bUUID = ByteArray(16)
rnd.nextBytes(bUUID)
Random().nextBytes(bUUID)
val uuid = Types.bytestoUUID(bUUID)
val eUUID = Types.UUIDtoBytes(uuid)
val uuid = bytes16ToUuid(bUUID)
val eUUID = uuidTo16Bytes(uuid)
val lUUID = bytes16ToUuid(bUUID)
val leUUID = uuidTo16Bytes(lUUID)
assertArrayEquals("UUID match failed", bUUID, eUUID)
assertArrayEquals("UUID match failed", bUUID, leUUID)
}
@Throws(Exception::class)
@@ -199,8 +184,8 @@ class TypesTest : TestCase() {
}
val bos = ByteArrayOutputStream()
val leos = LEDataOutputStream(bos)
leos.writeLong(Types.ULONG_MAX_VALUE)
val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(ULONG_MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()

View File

@@ -39,9 +39,8 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.CipherFactory
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.LEDataInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
class CipherTest : TestCase() {
private val rand = Random()
@@ -93,7 +92,7 @@ class CipherTest : TestCase() {
val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt)
val lis = LEDataInputStream(cis)
val lis = LittleEndianDataInputStream(cis)
val decrypttext = lis.readBytes(MESSAGE_LENGTH)

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass"
android:installLocation="auto">
<supports-screens
@@ -9,8 +10,8 @@
android:anyDensity="true" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:label="@string/app_name"
@@ -20,7 +21,10 @@
android:allowBackup="true"
android:fullBackupContent="@xml/backup"
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 -->
<meta-data
android:name="com.google.android.backup.api_key"
@@ -149,6 +153,10 @@
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name=".notifications.AttachmentFileNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
android:enabled="true"

View File

@@ -22,6 +22,7 @@ import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.Log
@@ -36,24 +37,33 @@ import androidx.appcompat.widget.Toolbar
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
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.EntryVersioned
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
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.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.SettingsAutofillActivity
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
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 java.util.*
import kotlin.collections.HashMap
class EntryActivity : LockingHideActivity() {
class EntryActivity : LockingActivity() {
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var titleIconView: ImageView? = null
@@ -64,11 +74,17 @@ class EntryActivity : LockingHideActivity() {
private var mDatabase: Database? = null
private var mEntry: EntryVersioned? = null
private var mEntry: Entry? = null
private var mIsHistory: Boolean = false
private var mEntryLastVersion: Entry? = null
private var mEntryHistoryPosition: Int = -1
private var mShowPassword: Boolean = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
private var clipboardHelper: ClipboardHelper? = null
private var firstLaunchOfActivity: Boolean = false
@@ -108,6 +124,21 @@ class EntryActivity : LockingHideActivity() {
// Init the clipboard helper
clipboardHelper = ClipboardHelper(this)
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()
}
}
// TODO Visual error for entry history
}
}
override fun onResume() {
@@ -115,13 +146,15 @@ class EntryActivity : LockingHideActivity() {
// Get Entry from UUID
try {
val keyEntry: PwNodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
val keyEntry: NodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
mEntry = mDatabase?.getEntryById(keyEntry)
mEntryLastVersion = mEntry
} catch (e: ClassCastException) {
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) {
mIsHistory = true
mEntry = mEntry?.getHistory()?.get(historyPosition)
@@ -155,10 +188,25 @@ class EntryActivity : LockingHideActivity() {
}
}
mAttachmentFileBinderManager?.apply {
registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
entryContentsView?.updateAttachmentDownloadProgress(attachment)
}
}
}
firstLaunchOfActivity = false
}
private fun fillEntryDataInContentsView(entry: EntryVersioned) {
override fun onPause() {
mAttachmentFileBinderManager?.unregisterProgressTask()
super.onPause()
}
private fun fillEntryDataInContentsView(entry: Entry) {
val database = Database.getInstance()
database.startManageEntry(entry)
@@ -265,6 +313,27 @@ class EntryActivity : LockingHideActivity() {
}
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
entryContentsView?.assignCreationDate(entry.creationTime)
entryContentsView?.assignModificationDate(entry.lastModificationTime)
@@ -276,9 +345,6 @@ class EntryActivity : LockingHideActivity() {
entryContentsView?.assignExpiresDate(getString(R.string.never))
}
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
// Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
if (mIsHistory) {
@@ -287,21 +353,26 @@ class EntryActivity : LockingHideActivity() {
taColorAccent.recycle()
}
val entryHistory = entry.getHistory()
// isMainEntry = not an history
// TODO isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) {
entryContentsView?.assignHistory(entryHistory)
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)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
// Not directly get the entry from intent data but from database
@@ -309,6 +380,15 @@ class EntryActivity : LockingHideActivity() {
fillEntryDataInContentsView(it)
}
}
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
}
}
}
}
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
@@ -327,9 +407,12 @@ class EntryActivity : LockingHideActivity() {
val inflater = menuInflater
MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database_lock, menu)
if (mReadOnly) {
inflater.inflate(R.menu.database, menu)
if (mIsHistory && !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
}
@@ -398,21 +481,18 @@ class EntryActivity : LockingHideActivity() {
MenuUtil.onContributionItemSelected(this)
return true
}
R.id.menu_toggle_pass -> {
mShowPassword = !mShowPassword
changeShowPasswordIcon(item)
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
return true
}
R.id.menu_edit -> {
mEntry?.let {
EntryEditActivity.launch(this@EntryActivity, it)
}
return true
}
R.id.menu_goto_url -> {
var url: String = mEntry?.url ?: ""
@@ -422,18 +502,33 @@ class EntryActivity : LockingHideActivity() {
}
UriUtil.gotoUrl(this, url)
return true
}
R.id.menu_restore_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseRestoreEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_delete_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseDeleteEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_lock -> {
lockAndExit()
return true
}
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)
}
return super.onOptionsItemSelected(item)
}
@@ -455,7 +550,7 @@ class EntryActivity : LockingHideActivity() {
const val KEY_ENTRY = "KEY_ENTRY"
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)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId)

View File

@@ -28,13 +28,16 @@ import android.view.MenuItem
import android.view.View
import android.widget.ScrollView
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.*
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.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
@@ -46,9 +49,10 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView
import com.kunzisoft.keepass.view.asError
import java.util.*
class EntryEditActivity : LockingHideActivity(),
class EntryEditActivity : LockingActivity(),
IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener {
@@ -56,20 +60,18 @@ class EntryEditActivity : LockingHideActivity(),
private var mDatabase: Database? = null
// Refs of an entry and group in database, are not modifiable
private var mEntry: EntryVersioned? = null
private var mParent: GroupVersioned? = null
private var mEntry: Entry? = null
private var mParent: Group? = null
// 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
// Views
private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: ScrollView? = null
private var entryEditContentsView: EntryEditContentsView? = null
private var saveView: View? = null
// Dialog thread
private var progressDialogThread: ProgressDialogThread? = null
// Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null
@@ -83,6 +85,8 @@ class EntryEditActivity : LockingHideActivity(),
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
scrollView = findViewById(R.id.entry_edit_scroll)
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
@@ -98,7 +102,7 @@ class EntryEditActivity : LockingHideActivity(),
mDatabase = Database.getInstance()
// 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
// Create an Entry copy to modify from the database entry
mEntry = mDatabase?.getEntryById(it)
@@ -118,7 +122,7 @@ class EntryEditActivity : LockingHideActivity(),
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
mEntry?.let { entry ->
// Create a copy to modify
mNewEntry = EntryVersioned(entry).also { newEntry ->
mNewEntry = Entry(entry).also { newEntry ->
// WARNING Remove the parent to keep memory with parcelable
newEntry.removeParent()
}
@@ -127,7 +131,7 @@ class EntryEditActivity : LockingHideActivity(),
}
// Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let {
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
mIsNew = true
// Create an empty new entry
if (savedInstanceState == null
@@ -176,7 +180,7 @@ class EntryEditActivity : LockingHideActivity(),
entryEditActivityEducation = EntryEditActivityEducation(this)
// Create progress dialog
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
mProgressDialogThread?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
@@ -184,10 +188,19 @@ class EntryEditActivity : LockingHideActivity(),
finish()
}
}
// Show error
if (!result.isSuccess) {
result.message?.let { resultMessage ->
Snackbar.make(coordinatorLayout!!,
resultMessage,
Snackbar.LENGTH_LONG).asError().show()
}
}
}
}
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
private fun populateViewsWithEntry(newEntry: Entry) {
// Don't start the field reference manager, we want to see the raw ref
mDatabase?.stopManageEntry(newEntry)
@@ -209,7 +222,7 @@ class EntryEditActivity : LockingHideActivity(),
}
}
private fun populateEntryWithViews(newEntry: EntryVersioned) {
private fun populateEntryWithViews(newEntry: Entry) {
mDatabase?.startManageEntry(newEntry)
@@ -231,7 +244,7 @@ class EntryEditActivity : LockingHideActivity(),
mDatabase?.stopManageEntry(newEntry)
}
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) {
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
mNewEntry?.icon = icon
mDatabase?.drawFactory?.let { iconDrawFactory ->
entryEditContentsView?.setIcon(iconDrawFactory, icon)
@@ -265,26 +278,26 @@ class EntryEditActivity : LockingHideActivity(),
// WARNING Add the parent previously deleted
newEntry.parent = mEntry?.parent
// Build info
newEntry.lastAccessTime = PwDate()
newEntry.lastModificationTime = PwDate()
newEntry.lastAccessTime = DateInstant()
newEntry.lastModificationTime = DateInstant()
populateEntryWithViews(newEntry)
// Open a progress dialog and save entry
if (mIsNew) {
mParent?.let { parent ->
progressDialogThread?.startDatabaseCreateEntry(
mProgressDialogThread?.startDatabaseCreateEntry(
newEntry,
parent,
!mReadOnly
!mReadOnly && mAutoSaveEnable
)
}
} else {
mEntry?.let { oldEntry ->
progressDialogThread?.startDatabaseUpdateEntry(
mProgressDialogThread?.startDatabaseUpdateEntry(
oldEntry,
newEntry,
!mReadOnly
!mReadOnly && mAutoSaveEnable
)
}
}
@@ -292,25 +305,16 @@ class EntryEditActivity : LockingHideActivity(),
}
}
override fun onResume() {
super.onResume()
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
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)
inflater.inflate(R.menu.edit_entry, menu)
if (mDatabase?.allowOTP == true)
inflater.inflate(R.menu.entry_otp, menu)
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
@@ -351,12 +355,13 @@ class EntryEditActivity : LockingHideActivity(),
lockAndExit()
return true
}
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
}
R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
}
R.id.menu_add_otp -> {
// Retrieve the current otpElement if exists
// and open the dialog to set up the OTP
@@ -364,7 +369,6 @@ class EntryEditActivity : LockingHideActivity(),
.show(supportFragmentManager, "addOTPDialog")
return true
}
android.R.id.home -> finish()
}
@@ -452,7 +456,7 @@ class EntryEditActivity : LockingHideActivity(),
* @param activity from activity
* @param pwEntry Entry to update
*/
fun launch(activity: Activity, pwEntry: EntryVersioned) {
fun launch(activity: Activity, pwEntry: Entry) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
@@ -466,7 +470,7 @@ class EntryEditActivity : LockingHideActivity(),
* @param activity from activity
* @param pwGroup Group who will contains new entry
*/
fun launch(activity: Activity, pwGroup: GroupVersioned) {
fun launch(activity: Activity, pwGroup: Group) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, pwGroup.nodeId)

View File

@@ -42,7 +42,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
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.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
@@ -53,8 +52,7 @@ import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.*
import java.io.FileNotFoundException
@@ -76,7 +74,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
private var mOpenFileHelper: OpenFileHelper? = null
private var progressDialogThread: ProgressDialogThread? = null
private var mProgressDialogThread: ProgressDialogThread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -92,17 +90,14 @@ class FileDatabaseSelectActivity : StylishActivity(),
// Create button
createButtonView = findViewById(R.id.create_database_button)
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}.resolveActivity(packageManager) == null) {
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
else{
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE
}
else{
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
createButtonView?.setOnClickListener { createNewFile() }
@@ -163,13 +158,15 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Attach the dialog thread to this activity
progressDialogThread = ProgressDialogThread(this) { actionTask, _ ->
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, _ ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
// TODO Check
// mAdapterDatabaseHistory?.notifyDataSetChanged()
// updateFileListVisibility()
GroupActivity.launch(this)
GroupActivity.launch(this@FileDatabaseSelectActivity)
}
}
}
}
@@ -180,18 +177,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
*/
@SuppressLint("InlinedApi")
private fun createNewFile() {
try {
startActivityForResult(Intent(
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")
}
createDocument(this, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass")
}
private fun fileNoFoundAction(e: FileNotFoundException) {
@@ -296,12 +283,12 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Register progress task
progressDialogThread?.registerProgressTask()
mProgressDialogThread?.registerProgressTask()
}
override fun onPause() {
// Unregister progress task
progressDialogThread?.unregisterProgressTask()
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
}
@@ -329,7 +316,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
mDatabaseFileUri?.let { databaseUri ->
// Create the new database
progressDialogThread?.startDatabaseCreate(
mProgressDialogThread?.startDatabaseCreate(
databaseUri,
masterPasswordChecked,
masterPassword,
@@ -365,8 +352,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Retrieve the created URI from the file manager
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
mDatabaseFileUri = data?.data
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
@@ -425,8 +412,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
private const val EXTRA_STAY = "EXTRA_STAY"
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
private const val CREATE_FILE_REQUEST_CODE = 3853
/*
* -------------------------
* No Standard Launch, pass by PasswordActivity

View File

@@ -42,18 +42,23 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentManager
import com.google.android.material.snackbar.Snackbar
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.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.ReadOnlyDialog
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.Database
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.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.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -77,6 +82,7 @@ class GroupActivity : LockingActivity(),
IconPickerDialogFragment.IconPickerListener,
ListNodesFragment.NodeClickListener,
ListNodesFragment.NodesActionMenuListener,
DeleteNodesDialogFragment.DeleteNodeListener,
ListNodesFragment.OnScrollListener,
SortDialogFragment.SortSelectionListener {
@@ -96,14 +102,10 @@ class GroupActivity : LockingActivity(),
private var mListNodesFragment: ListNodesFragment? = null
private var mCurrentGroupIsASearch: Boolean = false
private var progressDialogThread: ProgressDialogThread? = null
// Nodes
private var mRootGroup: GroupVersioned? = null
private var mCurrentGroup: GroupVersioned? = null
private var mOldGroupToUpdate: GroupVersioned? = null
// TODO private var mNodeToCopy: NodeVersioned? = null
// TODO private var mNodeToMove: NodeVersioned? = null
private var mRootGroup: Group? = null
private var mCurrentGroup: Group? = null
private var mOldGroupToUpdate: Group? = null
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
@@ -134,14 +136,6 @@ class GroupActivity : LockingActivity(),
toolbar?.title = ""
setSupportActionBar(toolbar)
/*
toolbarAction?.setNavigationOnClickListener {
toolbarAction?.collapse()
mNodeToCopy = null
mNodeToMove = null
}
*/
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
@@ -207,13 +201,13 @@ class GroupActivity : LockingActivity(),
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database)
// 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 ->
oldNodes = getListNodesFromBundle(database, oldNodesBundle)
}
var newNodes: List<NodeVersioned> = ArrayList()
var newNodes: List<Node> = ArrayList()
result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle ->
newNodes = getListNodesFromBundle(database, newNodesBundle)
}
@@ -234,34 +228,35 @@ class GroupActivity : LockingActivity(),
ACTION_DATABASE_DELETE_NODES_TASK -> {
if (result.isSuccess) {
// Rebuild all the list the avoid bug when delete node from db sort
if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB) {
// Rebuild all the list to avoid bug when delete node from sort
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
if (database.isRecycleBinEnabled) {
val recycleBin = database.recycleBin
if (mCurrentGroup != null && recycleBin != null
&& mCurrentGroup!!.parent == null
&& mCurrentGroup != recycleBin) {
if (mListNodesFragment?.contains(recycleBin) == true)
val currentGroup = mCurrentGroup
if (currentGroup != null && recycleBin != null
&& currentGroup != recycleBin) {
// Recycle bin already here, simply update it
if (mListNodesFragment?.contains(recycleBin) == true) {
mListNodesFragment?.updateNode(recycleBin)
else
}
// Recycle bin not here, verify if parents are similar to add it
else if (currentGroup == recycleBin.parent) {
mListNodesFragment?.addNode(recycleBin)
}
}
}
}
}
}
if (!result.isSuccess) {
result.exception?.errorId?.let { errorId ->
coordinatorLayout?.let { coordinatorLayout ->
result.exception?.errorId?.let { errorId ->
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
} ?: result.message?.let { message ->
Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).asError().show()
}
}
}
@@ -291,7 +286,7 @@ class GroupActivity : LockingActivity(),
}
}
private fun openSearchGroup(group: GroupVersioned?) {
private fun openSearchGroup(group: Group?) {
// Delete the previous search fragment
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
if (searchFragment != null) {
@@ -303,11 +298,11 @@ class GroupActivity : LockingActivity(),
openGroup(group, true)
}
private fun openChildGroup(group: GroupVersioned) {
private fun openChildGroup(group: Group) {
openGroup(group, false)
}
private fun openGroup(group: GroupVersioned?, isASearch: Boolean) {
private fun openGroup(group: Group?, isASearch: Boolean) {
// Check TimeoutHelper
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
// Open a group in a new fragment
@@ -347,7 +342,7 @@ class GroupActivity : LockingActivity(),
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
mReadOnly = mDatabase?.isReadOnly == true || mReadOnly
@@ -358,7 +353,7 @@ class GroupActivity : LockingActivity(),
}
// else a real group
else {
var pwGroupId: PwNodeId<*>? = null
var pwGroupId: NodeId<*>? = null
if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY)
} else {
@@ -367,7 +362,7 @@ class GroupActivity : LockingActivity(),
}
Log.w(TAG, "Creating tree view")
val currentGroup: GroupVersioned?
val currentGroup: Group?
currentGroup = if (pwGroupId == null) {
mRootGroup
} else {
@@ -440,12 +435,8 @@ class GroupActivity : LockingActivity(),
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
mCurrentGroup?.let {
val isRoot = it == mRootGroup
if (!it.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled
if (isRoot) {
showWarnings()
}
addEntryEnabled = it != mRootGroup && addEntryEnabled
}
enableAddGroup(addGroupEnabled)
enableAddEntry(addEntryEnabled)
@@ -458,7 +449,7 @@ class GroupActivity : LockingActivity(),
private fun refreshNumberOfChildren() {
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
text = mCurrentGroup?.getChildEntries(*Group.ChildFilter.getDefaults(context))?.size?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
@@ -470,16 +461,16 @@ class GroupActivity : LockingActivity(),
addNodeButtonView?.hideButtonOnScrollListener(dy)
}
override fun onNodeClick(node: NodeVersioned) {
override fun onNodeClick(node: Node) {
when (node.type) {
Type.GROUP -> try {
openChildGroup(node as GroupVersioned)
openChildGroup(node as Group)
} catch (e: ClassCastException) {
Log.e(TAG, "Node can't be cast in Group")
}
Type.ENTRY -> try {
val entryVersioned = node as EntryVersioned
val entryVersioned = node as Entry
EntrySelectionHelper.doEntrySelectionAction(intent,
{
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
@@ -517,7 +508,7 @@ class GroupActivity : LockingActivity(),
actionNodeMode = null
}
override fun onNodeSelected(nodes: List<NodeVersioned>): Boolean {
override fun onNodeSelected(nodes: List<Node>): Boolean {
if (nodes.isNotEmpty()) {
if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) {
mListNodesFragment?.actionNodesCallback(nodes, this)?.let {
@@ -532,34 +523,34 @@ class GroupActivity : LockingActivity(),
return true
}
override fun onOpenMenuClick(node: NodeVersioned): Boolean {
override fun onOpenMenuClick(node: Node): Boolean {
finishNodeAction()
onNodeClick(node)
return true
}
override fun onEditMenuClick(node: NodeVersioned): Boolean {
override fun onEditMenuClick(node: Node): Boolean {
finishNodeAction()
when (node.type) {
Type.GROUP -> {
mOldGroupToUpdate = node as GroupVersioned
mOldGroupToUpdate = node as Group
GroupEditDialogFragment.build(mOldGroupToUpdate!!)
.show(supportFragmentManager,
GroupEditDialogFragment.TAG_CREATE_GROUP)
}
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as EntryVersioned)
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as Entry)
}
return true
}
override fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean {
override fun onCopyMenuClick(nodes: List<Node>): Boolean {
actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally
return true
}
override fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean {
override fun onMoveMenuClick(nodes: List<Node>): Boolean {
actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally
@@ -567,56 +558,88 @@ class GroupActivity : LockingActivity(),
}
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
nodes: List<NodeVersioned>): Boolean {
nodes: List<Node>): Boolean {
// Move or copy only if allowed (in root if allowed)
if (mCurrentGroup != mDatabase?.rootGroup
|| mDatabase?.rootCanContainsEntry() == true) {
when (pasteMode) {
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
// Copy
mCurrentGroup?.let { newParent ->
progressDialogThread?.startDatabaseCopyNodes(
mProgressDialogThread?.startDatabaseCopyNodes(
nodes,
newParent,
!mReadOnly
!mReadOnly && mAutoSaveEnable
)
}
}
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
mCurrentGroup?.let { newParent ->
progressDialogThread?.startDatabaseMoveNodes(
mProgressDialogThread?.startDatabaseMoveNodes(
nodes,
newParent,
!mReadOnly
!mReadOnly && mAutoSaveEnable
)
}
}
else -> {}
else -> {
}
}
} else {
coordinatorLayout?.let { coordinatorLayout ->
Snackbar.make(coordinatorLayout,
R.string.error_copy_entry_here,
Snackbar.LENGTH_LONG).asError().show()
}
}
finishNodeAction()
return true
}
override fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean {
progressDialogThread?.startDatabaseDeleteNodes(
override fun onDeleteMenuClick(nodes: List<Node>): Boolean {
val database = mDatabase
// 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
!mReadOnly && mAutoSaveEnable
)
}
// else open the dialog to confirm deletion
else {
DeleteNodesDialogFragment.getInstance(nodes)
.show(supportFragmentManager, "deleteNodesDialogFragment")
}
finishNodeAction()
return true
}
override fun permanentlyDeleteNodes(nodes: List<Node>) {
mProgressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
)
}
override fun onResume() {
super.onResume()
// Refresh the elements
assignGroupViewElements()
// Refresh suggestions to change preferences
mSearchSuggestionAdapter?.reInit(this)
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause()
finishNodeAction()
@@ -626,12 +649,22 @@ class GroupActivity : LockingActivity(),
val inflater = menuInflater
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) {
inflater.inflate(R.menu.default_menu, 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
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
@@ -740,6 +773,17 @@ class GroupActivity : LockingActivity(),
lockAndExit()
return true
}
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
return true
}
R.id.menu_empty_recycle_bin -> {
mCurrentGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements
onDeleteMenuClick(listChildren)
}
return true
}
else -> {
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, mReadOnly, true)
@@ -750,7 +794,7 @@ class GroupActivity : LockingActivity(),
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?,
icon: PwIcon?) {
icon: IconImage?) {
if (name != null && name.isNotEmpty() && icon != null) {
when (action) {
@@ -764,28 +808,33 @@ class GroupActivity : LockingActivity(),
// Not really needed here because added in runnable but safe
newGroup.parent = currentGroup
progressDialogThread?.startDatabaseCreateGroup(
newGroup, currentGroup, !mReadOnly)
mProgressDialogThread?.startDatabaseCreateGroup(
newGroup,
currentGroup,
!mReadOnly && mAutoSaveEnable
)
}
}
}
GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> {
// If update add new elements
mOldGroupToUpdate?.let { oldGroupToUpdate ->
GroupVersioned(oldGroupToUpdate).let { updateGroup ->
val updateGroup = Group(oldGroupToUpdate).let { updateGroup ->
updateGroup.apply {
// WARNING remove parent and children to keep memory
removeParent()
removeChildren() // TODO concurrent exception
removeChildren()
title = name
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 -> {}
@@ -795,7 +844,7 @@ class GroupActivity : LockingActivity(),
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?,
icon: PwIcon?) {
icon: IconImage?) {
// Do nothing here
}
@@ -806,14 +855,6 @@ class GroupActivity : LockingActivity(),
.iconPicked(bundle)
}
private fun showWarnings() {
if (Database.getInstance().isReadOnly) {
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)
}
@@ -906,7 +947,7 @@ class GroupActivity : LockingActivity(),
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
private fun buildAndLaunchIntent(context: Context, group: GroupVersioned?, readOnly: Boolean,
private fun buildAndLaunchIntent(context: Context, group: Group?, readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
val checkTime = if (context is Activity)
TimeoutHelper.checkTimeAndLockIfTimeout(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.activities
import android.content.Context
@@ -18,16 +37,16 @@ import androidx.appcompat.view.ActionMode
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Type
import com.kunzisoft.keepass.database.element.node.Type
import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
@@ -36,7 +55,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private var onScrollListener: OnScrollListener? = null
private var listView: RecyclerView? = null
var mainGroup: GroupVersioned? = null
var mainGroup: Group? = null
private set
private var mAdapter: NodeAdapter? = null
@@ -44,8 +63,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
private set
private val listActionNodes = LinkedList<NodeVersioned>()
private val listPasteNodes = LinkedList<NodeVersioned>()
private val listActionNodes = LinkedList<Node>()
private val listPasteNodes = LinkedList<Node>()
private var notFoundView: View? = null
private var isASearchResult: Boolean = false
@@ -103,7 +122,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
mAdapter = NodeAdapter(context)
mAdapter?.apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(node: NodeVersioned) {
override fun onNodeClick(node: Node) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
@@ -120,7 +139,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
override fun onNodeLongClick(node: NodeVersioned): Boolean {
override fun onNodeLongClick(node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
@@ -228,8 +247,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (Database.getInstance().allowRecycleBin
&& Database.getInstance().isRecycleBinEnabled) {
if (Database.getInstance().isRecycleBinEnabled) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
@@ -251,7 +269,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
fun actionNodesCallback(nodes: List<NodeVersioned>,
fun actionNodesCallback(nodes: List<Node>,
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
return object : ActionMode.Callback {
@@ -276,7 +294,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (readOnly || nodes[0] == database.recycleBin) {
if (readOnly
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
menu?.removeItem(R.id.menu_edit)
}
} else {
@@ -287,7 +306,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Copy and Move (not for groups)
if (readOnly
|| isASearchResult
|| nodes.any { it == database.recycleBin }
|| nodes.any { it.type == Type.GROUP }) {
// TODO COPY For Group
menu?.removeItem(R.id.menu_copy)
@@ -295,7 +313,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Deletion
if (readOnly || nodes.any { it == database.recycleBin }) {
if (readOnly
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
menu?.removeItem(R.id.menu_delete)
}
}
@@ -354,7 +373,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
if (resultCode == EntryEditActivity.ADD_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 { newNode ->
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter?.addNode(newNode)
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
@@ -369,31 +388,31 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
fun contains(node: NodeVersioned): Boolean {
fun contains(node: Node): Boolean {
return mAdapter?.contains(node) ?: false
}
fun addNode(newNode: NodeVersioned) {
fun addNode(newNode: Node) {
mAdapter?.addNode(newNode)
}
fun addNodes(newNodes: List<NodeVersioned>) {
fun addNodes(newNodes: List<Node>) {
mAdapter?.addNodes(newNodes)
}
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) {
fun updateNode(oldNode: Node, newNode: Node? = null) {
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)
}
fun removeNode(pwNode: NodeVersioned) {
fun removeNode(pwNode: Node) {
mAdapter?.removeNode(pwNode)
}
fun removeNodes(nodes: List<NodeVersioned>) {
fun removeNodes(nodes: List<Node>) {
mAdapter?.removeNodes(nodes)
}
@@ -409,20 +428,20 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickListener {
fun onNodeClick(node: NodeVersioned)
fun onNodeSelected(nodes: List<NodeVersioned>): Boolean
fun onNodeClick(node: Node)
fun onNodeSelected(nodes: List<Node>): Boolean
}
/**
* Menu listener to redefine to do an action in menu
*/
interface NodesActionMenuListener {
fun onOpenMenuClick(node: NodeVersioned): Boolean
fun onEditMenuClick(node: NodeVersioned): Boolean
fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean
fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean
fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<NodeVersioned>): Boolean
fun onOpenMenuClick(node: Node): Boolean
fun onEditMenuClick(node: Node): Boolean
fun onCopyMenuClick(nodes: List<Node>): Boolean
fun onMoveMenuClick(nodes: List<Node>): Boolean
fun onDeleteMenuClick(nodes: List<Node>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
}
enum class PasteMode {
@@ -447,7 +466,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private const val GROUP_KEY = "GROUP_KEY"
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()
if (group != null) {
bundle.putParcelable(GROUP_KEY, group)

View File

@@ -24,6 +24,7 @@ import android.app.assist.AssistStructure
import android.app.backup.BackupManager
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
@@ -37,13 +38,11 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.Button
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.TextView
import android.widget.*
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
@@ -58,7 +57,7 @@ import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.database.action.ProgressDialogThread
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.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
@@ -99,9 +98,10 @@ class PasswordActivity : StylishActivity() {
private var mRememberKeyFile: Boolean = false
private var mOpenFileHelper: OpenFileHelper? = null
private var mPermissionAsked = false
private var readOnly: Boolean = false
private var progressDialogThread: ProgressDialogThread? = null
private var mProgressDialogThread: ProgressDialogThread? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
@@ -130,6 +130,7 @@ class PasswordActivity : StylishActivity() {
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
advancedUnlockInfoView = findViewById(R.id.biometric_info)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
val browseView = findViewById<View>(R.id.open_database_button)
@@ -163,7 +164,8 @@ class PasswordActivity : StylishActivity() {
enableOrNotTheConfirmationButton()
}
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck biometric if error
@@ -188,7 +190,7 @@ class PasswordActivity : StylishActivity() {
resultError = resultException.getLocalizedMessage(resources)
// Relaunch loading if we need to fix UUID
if (resultException is LoadDatabaseDuplicateUuidException) {
if (resultException is DuplicateUuidDatabaseException) {
showLoadDatabaseDuplicateUuidMessage {
var databaseUri: Uri? = null
@@ -231,6 +233,7 @@ class PasswordActivity : StylishActivity() {
}
}
}
}
private fun launchGroupActivity() {
EntrySelectionHelper.doEntrySelectionAction(intent,
@@ -272,12 +275,13 @@ class PasswordActivity : StylishActivity() {
// For check shutdown
super.onResume()
progressDialogThread?.registerProgressTask()
mProgressDialogThread?.registerProgressTask()
initUriFromIntent()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState)
}
@@ -399,7 +403,7 @@ class PasswordActivity : StylishActivity() {
}
})
}
advancedUnlockedManager?.initBiometric()
advancedUnlockedManager?.checkBiometricAvailability()
biometricInitialize = true
} else {
advancedUnlockedManager?.destroy()
@@ -459,7 +463,7 @@ class PasswordActivity : StylishActivity() {
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
}
@@ -527,7 +531,7 @@ class PasswordActivity : StylishActivity() {
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) {
progressDialogThread?.startDatabaseLoad(
mProgressDialogThread?.startDatabaseLoad(
databaseUri,
password,
keyFile,
@@ -543,9 +547,6 @@ class PasswordActivity : StylishActivity() {
}.show(supportFragmentManager, "duplicateUUIDDialog")
}
// To fix multiple view education
private var performedEductionInProgress = false
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
// Read menu
@@ -561,51 +562,96 @@ class PasswordActivity : StylishActivity() {
super.onCreateOptionsMenu(menu)
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
launchEducation(menu) {
launchCheckPermission()
}
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) {
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
}
}
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu) {
val educationContainerView = containerView
val unlockEducationPerformed = educationContainerView != null
menu: Menu,
onEducationFinished: ()-> Unit) {
val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
educationContainerView,
educationToolbar,
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!unlockEducationPerformed) {
val educationToolbar = toolbar
val readOnlyEducationPerformed =
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
educationToolbar.findViewById(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) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
// EducationPerformed
val biometricEducationPerformed =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!)
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!biometricEducationPerformed) {
onEducationFinished.invoke()
}
}
}
}
@@ -681,6 +727,10 @@ class PasswordActivity : StylishActivity() {
private const val KEY_PASSWORD = "password"
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?,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java)

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

@@ -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.UPDATE
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
@@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
private var editGroupDialogAction: EditGroupDialogAction? = null
private var nameGroup: String? = null
private var iconGroup: PwIcon? = null
private var iconGroup: IconImage? = null
private var nameTextLayoutView: TextInputLayout? = null
private var nameTextView: TextView? = null
@@ -186,8 +186,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
}
interface EditGroupListener {
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
}
companion object {
@@ -206,7 +206,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
return fragment
}
fun build(group: GroupVersioned): GroupEditDialogFragment {
fun build(group: Group): GroupEditDialogFragment {
val bundle = Bundle()
bundle.putString(KEY_NAME, group.title)
bundle.putParcelable(KEY_ICON, group.icon)

View File

@@ -35,7 +35,7 @@ import android.widget.GridView
import android.widget.ImageView
import com.kunzisoft.keepass.R
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.IconPackChooser
@@ -72,7 +72,7 @@ class IconPickerDialogFragment : DialogFragment() {
currIconGridView.setOnItemClickListener { _, _, position, _ ->
val bundle = Bundle()
bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position))
bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
iconPickerListener?.iconPicked(bundle)
dismiss()
}
@@ -128,7 +128,7 @@ class IconPickerDialogFragment : DialogFragment() {
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)
}

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

@@ -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.dialogs
import android.annotation.SuppressLint
@@ -29,6 +48,7 @@ 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() {
@@ -246,7 +266,7 @@ class SetOTPDialogFragment : DialogFragment() {
override fun afterTextChanged(s: Editable?) {
s?.toString()?.let { userString ->
try {
mOtpElement.setBase32Secret(userString)
mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH))
otpSecretContainer?.error = null
} catch (exception: Exception) {
otpSecretContainer?.error = getString(R.string.error_otp_secret_key)

View File

@@ -29,7 +29,7 @@ import android.view.View
import android.widget.CompoundButton
import android.widget.RadioGroup
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.SortNodeEnum
class SortDialogFragment : DialogFragment() {

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
import android.app.assist.AssistStructure

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
import android.content.Context

View File

@@ -32,6 +32,7 @@ import android.view.ViewGroup
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
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.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -63,6 +64,10 @@ abstract class LockingActivity : StylishActivity() {
return field || mSelectionMode
}
protected var mSelectionMode: Boolean = false
protected var mAutoSaveEnable: Boolean = true
var mProgressDialogThread: ProgressDialogThread? = null
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -86,6 +91,8 @@ abstract class LockingActivity : StylishActivity() {
mExitLock = false
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
mProgressDialogThread = ProgressDialogThread(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -101,8 +108,13 @@ abstract class LockingActivity : StylishActivity() {
override fun onResume() {
super.onResume()
mProgressDialogThread?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
invalidateOptionsMenu()
if (mTimeoutEnable) {
// End activity if database not loaded
@@ -119,8 +131,6 @@ abstract class LockingActivity : StylishActivity() {
if (!mExitLock)
TimeoutHelper.recordTime(this)
}
invalidateOptionsMenu()
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -130,6 +140,8 @@ abstract class LockingActivity : StylishActivity() {
}
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
if (mTimeoutEnable) {

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

@@ -61,6 +61,7 @@ object Stylish {
return when (themeString) {
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_blue) -> R.style.KeepassDXStyle_Blue
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red

View File

@@ -19,20 +19,45 @@
*/
package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
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() {
@StyleRes
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?) {
super.onCreate(savedInstanceState)
this.themeId = Stylish.getThemeId(this)
setTheme(themeId)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
override fun onResume() {

View File

@@ -0,0 +1,96 @@
/*
* 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.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
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 (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
import android.content.Context
@@ -7,13 +26,13 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
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>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryHistoryList: MutableList<EntryVersioned> = ArrayList()
var onItemClickListener: ((item: EntryVersioned, position: Int)->Unit)? = null
var entryHistoryList: MutableList<Entry> = ArrayList()
var onItemClickListener: ((item: Entry, position: Int)->Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
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
import android.content.Context
@@ -15,7 +34,7 @@ import java.util.ArrayList
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var fields: MutableList<Field> = ArrayList()
private var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
@@ -33,6 +52,11 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
return fields.size
}
fun setFields(fieldsToAdd: List<Field>) {
fields.clear()
fields.addAll(fieldsToAdd)
}
fun clear() {
fields.clear()
}

View File

@@ -34,10 +34,15 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.Database
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.Type
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.strikeOut
import java.util.*
class NodeAdapter
@@ -48,7 +53,7 @@ class NodeAdapter
(private val context: Context)
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
private val nodeSortedList: SortedList<NodeVersioned>
private val nodeSortedList: SortedList<Node>
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
@@ -64,8 +69,9 @@ class NodeAdapter
private var recycleBinBottomSort: Boolean = true
private var showUserNames: 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 val mDatabase: Database
@@ -83,18 +89,18 @@ class NodeAdapter
init {
assignPreferences()
this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback<NodeVersioned>(this) {
override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int {
this.nodeSortedList = SortedList(Node::class.java, object : SortedListAdapterCallback<Node>(this) {
override fun compare(item1: Node, item2: Node): Int {
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
}
override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean {
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: NodeVersioned, item2: NodeVersioned): Boolean {
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
return item1 == item2
}
})
@@ -126,6 +132,8 @@ class NodeAdapter
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
this.entryFilters = Group.ChildFilter.getDefaults(context)
// Reinit textSize for all view type
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
}
@@ -133,11 +141,11 @@ class NodeAdapter
/**
* Rebuild the list by clear and build children from the group
*/
fun rebuildList(group: GroupVersioned) {
fun rebuildList(group: Group) {
this.nodeSortedList.clear()
assignPreferences()
try {
this.nodeSortedList.addAll(group.getChildren())
this.nodeSortedList.addAll(group.getChildren(*entryFilters))
} 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()
@@ -145,7 +153,7 @@ class NodeAdapter
notifyDataSetChanged()
}
fun contains(node: NodeVersioned): Boolean {
fun contains(node: Node): Boolean {
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
}
@@ -153,7 +161,7 @@ class NodeAdapter
* Add a node to the list
* @param node Node to add
*/
fun addNode(node: NodeVersioned) {
fun addNode(node: Node) {
nodeSortedList.add(node)
}
@@ -161,7 +169,7 @@ class NodeAdapter
* Add nodes to the list
* @param nodes Nodes to add
*/
fun addNodes(nodes: List<NodeVersioned>) {
fun addNodes(nodes: List<Node>) {
nodeSortedList.addAll(nodes)
}
@@ -169,7 +177,7 @@ class NodeAdapter
* Remove a node in the list
* @param node Node to delete
*/
fun removeNode(node: NodeVersioned) {
fun removeNode(node: Node) {
nodeSortedList.remove(node)
}
@@ -177,7 +185,7 @@ class NodeAdapter
* Remove nodes in the list
* @param nodes Nodes to delete
*/
fun removeNodes(nodes: List<NodeVersioned>) {
fun removeNodes(nodes: List<Node>) {
nodes.forEach { node ->
nodeSortedList.remove(node)
}
@@ -209,7 +217,7 @@ class NodeAdapter
* @param oldNode Node before the update
* @param newNode Node after the update
*/
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) {
fun updateNode(oldNode: Node, newNode: Node) {
nodeSortedList.beginBatchedUpdates()
nodeSortedList.remove(oldNode)
nodeSortedList.add(newNode)
@@ -221,7 +229,7 @@ class NodeAdapter
* @param oldNodes Nodes before 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()
oldNodes.forEach { oldNode ->
nodeSortedList.remove(oldNode)
@@ -230,11 +238,11 @@ class NodeAdapter
nodeSortedList.endBatchedUpdates()
}
fun notifyNodeChanged(node: NodeVersioned) {
fun notifyNodeChanged(node: Node) {
notifyItemChanged(nodeSortedList.indexOf(node))
}
fun setActionNodes(actionNodes: List<NodeVersioned>) {
fun setActionNodes(actionNodes: List<Node>) {
this.actionNodesList.apply {
clear()
addAll(actionNodes)
@@ -287,15 +295,53 @@ class NodeAdapter
width = iconSize.toInt()
}
}
// Assign text
holder.text.apply {
text = subNode.title
setTextSize(textSizeUnit, infoTextSize)
paintFlags = if (subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
strikeOut(subNode.isCurrentlyExpires)
}
// 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, subtextSize)
}
}
mDatabase.stopManageEntry(entry)
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group)
.getChildEntries(*entryFilters)
.size.toString()
setTextSize(textSizeUnit, numberChildrenTextSize)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
// Assign click
holder.container.setOnClickListener {
nodeClickCallback?.onNodeClick(subNode)
@@ -305,46 +351,8 @@ class NodeAdapter
}
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 {
return nodeSortedList.size()
@@ -361,8 +369,8 @@ class NodeAdapter
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickCallback {
fun onNodeClick(node: NodeVersioned)
fun onNodeLongClick(node: NodeVersioned): Boolean
fun onNodeClick(node: Node)
fun onNodeLongClick(node: Node): Boolean
}
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@@ -22,22 +22,20 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.database.Cursor
import android.graphics.Color
import androidx.cursoradapter.widget.CursorAdapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.cursor.EntryCursor
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.icons.assignDatabaseIcon
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) {
private val cursorInflater: LayoutInflater = context.getSystemService(
@@ -72,34 +70,31 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
override fun bindView(view: View, context: Context, cursor: Cursor) {
// Retrieve elements from cursor
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))
val iconFactory = database.iconFactory
var icon: PwIcon = iconFactory.getIcon(
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))))
if (icon.isUnknown) {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)))
if (icon.isUnknown)
icon = iconFactory.keyIcon
}
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))
database.getEntryFrom(cursor)?.let { currentEntry ->
val viewHolder = view.tag as ViewHolder
// Assign image
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
viewHolder.imageViewIcon?.assignDatabaseIcon(
database.drawFactory,
currentEntry.icon,
iconColor)
// Assign title
val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString())
viewHolder.textViewTitle?.text = showTitle
if (displayUsername && username.isNotEmpty()) {
viewHolder.textViewSubTitle?.text = String.format("(%s)", username)
viewHolder.textViewTitle?.apply {
text = currentEntry.getVisualTitle()
strikeOut(currentEntry.isCurrentlyExpires)
}
// Assign subtitle
viewHolder.textViewSubTitle?.apply {
val entryUsername = currentEntry.username
text = if (displayUsername && entryUsername.isNotEmpty()) {
String.format("(%s)", entryUsername)
} else {
viewHolder.textViewSubTitle?.text = ""
""
}
strikeOut(currentEntry.isCurrentlyExpires)
}
}
}
@@ -110,11 +105,11 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
}
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return database.searchEntries(constraint.toString())
return database.searchEntries(context, constraint.toString())
}
fun getEntryFromPosition(position: Int): EntryVersioned? {
var pwEntry: EntryVersioned? = null
fun getEntryFromPosition(position: Int): Entry? {
var pwEntry: Entry? = null
val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) {

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
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
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
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
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
import android.os.Parcel

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
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
import androidx.room.ColumnInfo

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.biometric
import android.content.Intent
@@ -10,6 +29,7 @@ import android.view.MenuInflater
import android.widget.CompoundButton
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricConstants
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
@@ -36,61 +56,54 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
// fingerprint related code here
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)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
}
init {
// Add a check listener to change fingerprint mode
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
checkBiometricAvailability()
// Add old listener to enable the button, only be call here because of onCheckedChange bug
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
}
checkBiometricAvailability()
}
@Synchronized
private fun checkBiometricAvailability() {
/**
* Check biometric availability and change the current mode depending of device's state
*/
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
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate()
if (!PreferencesUtil.isBiometricUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.UNAVAILABLE)
} else {
// fingerprint is available but not configured, show icon but in disabled state with some information
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.NOT_CONFIGURED)
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
} else {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
}
// Recheck to change the mode
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
} else {
if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
toggleMode(Mode.STORE)
toggleMode(Mode.STORE_CREDENTIAL)
} else {
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
// fingerprint available but no stored password found yet for this DB so show info don't listen
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode( if (containsCipher) {
// listen for decryption
Mode.OPEN
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
@@ -100,6 +113,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
}
}
}
private fun toggleMode(newBiometricMode: Mode) {
if (newBiometricMode != biometricMode) {
@@ -129,17 +143,15 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
context.runOnUiThread {
when (biometricMode) {
Mode.UNAVAILABLE -> {
}
Mode.NOT_CONFIGURED -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE -> {
Mode.UNAVAILABLE -> {}
Mode.BIOMETRIC_NOT_CONFIGURED -> {}
Mode.KEY_MANAGER_UNAVAILABLE -> {}
Mode.WAIT_CREDENTIAL -> {}
Mode.STORE_CREDENTIAL -> {
// newly store the entered password in encrypted way
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
}
Mode.OPEN -> {
Mode.EXTRACT_CREDENTIAL -> {
// retrieve the encrypted value from preferences
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
it?.encryptedValue?.let { value ->
@@ -155,7 +167,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private fun initNotAvailable() {
showFingerPrintViews(false)
advancedUnlockInfoView?.setIconViewClickListener(null)
advancedUnlockInfoView?.setIconViewClickListener(false, null)
}
private fun initNotConfigured() {
@@ -163,7 +175,17 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener {
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))
}
}
@@ -173,7 +195,11 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
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?,
@@ -235,10 +261,11 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun initBiometricMode() {
when (biometricMode) {
Mode.UNAVAILABLE -> initNotAvailable()
Mode.NOT_CONFIGURED -> initNotConfigured()
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE -> initEncryptData()
Mode.OPEN -> initDecryptData()
Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
}
// Show fingerprint key deletion
context.invalidateOptionsMenu()
@@ -255,7 +282,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
if (!addBiometricMenuInProgress) {
addBiometricMenuInProgress = true
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.NOT_CONFIGURED)
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.BIOMETRIC_NOT_CONFIGURED)
&& it) {
menuInflater.inflate(R.menu.advanced_unlock, menu)
addBiometricMenuInProgress = false
@@ -267,7 +294,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun deleteEntryKey() {
biometricUnlockDatabaseHelper?.deleteEntryKey()
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri)
biometricMode = Mode.NOT_CONFIGURED
biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED
checkBiometricAvailability()
}
@@ -313,7 +340,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
enum class Mode {
UNAVAILABLE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN
UNAVAILABLE, BIOMETRIC_NOT_CONFIGURED, KEY_MANAGER_UNAVAILABLE, WAIT_CREDENTIAL, STORE_CREDENTIAL, EXTRACT_CREDENTIAL
}
companion object {

View File

@@ -52,13 +52,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private var keyguardManager: KeyguardManager? = null
private var cryptoObject: BiometricPrompt.CryptoObject? = null
private var isBiometricInit = false
private var isKeyManagerInit = false
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
var biometricUnlockCallback: BiometricUnlockCallback? = null
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
setConfirmationRequired(true)
// TODO device credential
/*
if (keyguardManager?.isDeviceSecure == true)
@@ -70,7 +71,8 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
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
/*
if (keyguardManager?.isDeviceSecure == true)
@@ -80,18 +82,18 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
setNegativeButtonText(context.getString(android.R.string.cancel))
}.build()
val isBiometricInitialized: Boolean
val isKeyManagerInitialized: Boolean
get() {
if (!isBiometricInit) {
if (!isKeyManagerInit) {
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
}
return isBiometricInit
return isKeyManagerInit
}
init {
if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) {
// really not much to do when no fingerprint support found
isBiometricInit = false
isKeyManagerInit = false
} else {
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
@@ -103,17 +105,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
+ BIOMETRIC_BLOCKS_MODES + "/"
+ BIOMETRIC_ENCRYPTION_PADDING)
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
isBiometricInit = true
isKeyManagerInit = (keyStore != null
&& keyGenerator != null
&& cipher != null)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize the keystore", e)
isBiometricInit = false
isKeyManagerInit = false
biometricUnlockCallback?.onBiometricException(e)
}
}
}
private fun getSecretKey(): SecretKey? {
if (!isBiometricInitialized) {
if (!isKeyManagerInitialized) {
return null
}
try {
@@ -155,7 +159,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isBiometricInitialized) {
if (!isKeyManagerInitialized) {
return
}
try {
@@ -176,11 +180,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
Log.e(TAG, "Unable to initialize encrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
}
}
fun encryptData(value: String) {
if (!isBiometricInitialized) {
if (!isKeyManagerInitialized) {
return
}
try {
@@ -197,14 +200,13 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
Log.e(TAG, "Unable to encrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
}
}
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isBiometricInitialized) {
if (!isKeyManagerInitialized) {
return
}
try {
@@ -229,11 +231,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
Log.e(TAG, "Unable to initialize decrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
}
}
fun decryptData(encryptedValue: String) {
if (!isBiometricInitialized) {
if (!isKeyManagerInitialized) {
return
}
try {
@@ -249,7 +250,6 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
Log.e(TAG, "Unable to decrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
}
}
fun deleteEntryKey() {

View File

@@ -20,43 +20,44 @@
package com.kunzisoft.keepass.biometric
import android.content.Context
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.RequiresApi
import android.widget.ImageView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.kunzisoft.keepass.R
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
private val scanFingerprint: AnimatedVectorDrawable =
context.getDrawable(R.drawable.scan_fingerprint) as AnimatedVectorDrawable
private val scanFingerprint: AnimatedVectorDrawableCompat? =
AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint)
init {
imageView.setImageDrawable(scanFingerprint)
}
private var animationCallback = object : Animatable2.AnimationCallback() {
private var animationCallback = object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable) {
if (!scanFingerprint.isRunning)
scanFingerprint.start()
imageView.post {
scanFingerprint?.start()
}
}
}
fun startScan() {
scanFingerprint.registerAnimationCallback(animationCallback)
scanFingerprint?.registerAnimationCallback(animationCallback)
if (!scanFingerprint.isRunning)
scanFingerprint.start()
if (scanFingerprint?.isRunning != true)
scanFingerprint?.start()
}
fun stopScan() {
scanFingerprint.unregisterAnimationCallback(animationCallback)
scanFingerprint?.unregisterAnimationCallback(animationCallback)
if (scanFingerprint.isRunning)
if (scanFingerprint?.isRunning == true)
scanFingerprint.stop()
}
}

View File

@@ -19,15 +19,13 @@
*/
package com.kunzisoft.keepass.crypto
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.longTo8Bytes
import java.io.IOException
import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Arrays
import java.util.*
import javax.crypto.Mac
import kotlin.math.min
@@ -60,7 +58,7 @@ object CryptoUtil {
throw RuntimeException(e)
}
val pbR = LEDataOutputStream.writeLongBuf(r)
val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)

View File

@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
@@ -41,13 +41,28 @@ class AesEngine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.AESRijndael
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
}
companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID(
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()))
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()))
}
}

View File

@@ -19,8 +19,8 @@
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.spongycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -44,13 +44,28 @@ class ChaCha20Engine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.ChaCha20
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.ChaCha20
}
companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID(
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()))
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()))
}
}

View File

@@ -19,7 +19,7 @@
*/
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.InvalidKeyException
@@ -46,6 +46,6 @@ abstract class CipherEngine {
return getCipher(opmode, key, IV, false)
}
abstract fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm
abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
}

View File

@@ -20,14 +20,12 @@
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.UUID
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
@@ -47,13 +45,28 @@ class TwofishEngine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.Twofish
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
}
companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID(
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()))
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()))
}
}

View File

@@ -23,7 +23,7 @@ import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
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.security.SecureRandom
import java.util.*
@@ -32,7 +32,7 @@ class AesKdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters
get() {
return KdfParameters(uuid).apply {
return KdfParameters(uuid!!).apply {
setParamUUID()
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
}
@@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() {
private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = Types.bytestoUUID(
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xC9.toByte(),
0xD9.toByte(),
0xF3.toByte(),

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.Types
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -30,7 +30,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters
get() {
val p = KdfParameters(uuid)
val p = KdfParameters(uuid!!)
p.setParamUUID()
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
@@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID(
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
0x6D.toByte(),

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

@@ -32,9 +32,8 @@ open class AssignPasswordInDatabaseRunnable (
withMasterPassword: Boolean,
masterPassword: String?,
withKeyFile: Boolean,
keyFile: Uri?,
save: Boolean)
: SaveDatabaseRunnable(context, database, save) {
keyFile: Uri?)
: SaveDatabaseRunnable(context, database, true) {
private var mMasterPassword: String? = null
protected var mKeyFile: Uri? = null
@@ -59,7 +58,7 @@ open class AssignPasswordInDatabaseRunnable (
database.retrieveMasterKey(mMasterPassword, uriInputStream)
} catch (e: Exception) {
erase(mBackupKey)
setError(e.message)
setError(e)
}
super.onStartRun()

View File

@@ -28,24 +28,25 @@ import com.kunzisoft.keepass.database.element.Database
class CreateDatabaseRunnable(context: Context,
private val mDatabase: Database,
databaseUri: Uri,
private val databaseName: String,
private val rootName: String,
withMasterPassword: Boolean,
masterPassword: String?,
withKeyFile: Boolean,
keyFile: Uri?,
save: Boolean)
: AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile, save) {
keyFile: Uri?)
: AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) {
override fun onStartRun() {
try {
// Create new database record
mDatabase.apply {
createData(mDatabaseUri)
createData(mDatabaseUri, databaseName, rootName)
// Set Database state
loaded = true
}
} catch (e: Exception) {
mDatabase.closeAndClear()
setError(e.message)
setError(e)
}
super.onStartRun()

View File

@@ -25,7 +25,7 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
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.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -62,7 +62,7 @@ class LoadDatabaseRunnable(private val context: Context,
mFixDuplicateUUID,
progressTaskUpdater)
}
catch (e: LoadDatabaseDuplicateUuidException) {
catch (e: DuplicateUuidDatabaseException) {
mDuplicateUuidAction?.invoke(result)
setError(e)
}

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
import android.content.*
@@ -11,27 +30,35 @@ import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
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.DatabaseTaskNotificationService
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_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_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_LOAD_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_SAVE_COMPRESSION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DESCRIPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ENCRYPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ITERATIONS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_NAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_PARALLELISM_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_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_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
@@ -44,10 +71,10 @@ import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import java.util.*
import kotlin.collections.ArrayList
class ProgressDialogThread(private val activity: FragmentActivity) {
class ProgressDialogThread(private val activity: FragmentActivity,
var onActionFinish: (actionTask: String,
result: ActionRunnable.Result) -> Unit) {
var onActionFinish: ((actionTask: String,
result: ActionRunnable.Result) -> Unit)? = null
private var intentDatabaseTask = Intent(activity, DatabaseTaskNotificationService::class.java)
@@ -68,7 +95,7 @@ class ProgressDialogThread(private val activity: FragmentActivity,
}
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
onActionFinish.invoke(actionTask, result)
onActionFinish?.invoke(actionTask, result)
// Remove the progress task
ProgressTaskDialogFragment.stop(activity)
TimeoutHelper.releaseTemporarilyDisableTimeoutAndLockIfTimeout(activity)
@@ -172,7 +199,11 @@ class ProgressDialogThread(private val activity: FragmentActivity,
unBindService()
try {
activity.unregisterReceiver(databaseTaskBroadcastReceiver)
} catch (e: IllegalArgumentException) {
// If receiver not register, do nothing
}
}
@Synchronized
@@ -250,8 +281,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
----
*/
fun startDatabaseCreateGroup(newGroup: GroupVersioned,
parent: GroupVersioned,
fun startDatabaseCreateGroup(newGroup: Group,
parent: Group,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.GROUP_KEY, newGroup)
@@ -261,8 +292,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, ACTION_DATABASE_CREATE_GROUP_TASK)
}
fun startDatabaseUpdateGroup(oldGroup: GroupVersioned,
groupToUpdate: GroupVersioned,
fun startDatabaseUpdateGroup(oldGroup: Group,
groupToUpdate: Group,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.GROUP_ID_KEY, oldGroup.nodeId)
@@ -272,8 +303,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, ACTION_DATABASE_UPDATE_GROUP_TASK)
}
fun startDatabaseCreateEntry(newEntry: EntryVersioned,
parent: GroupVersioned,
fun startDatabaseCreateEntry(newEntry: Entry,
parent: Group,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, newEntry)
@@ -283,8 +314,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, ACTION_DATABASE_CREATE_ENTRY_TASK)
}
fun startDatabaseUpdateEntry(oldEntry: EntryVersioned,
entryToUpdate: EntryVersioned,
fun startDatabaseUpdateEntry(oldEntry: Entry,
entryToUpdate: Entry,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, oldEntry.nodeId)
@@ -295,20 +326,20 @@ class ProgressDialogThread(private val activity: FragmentActivity,
}
private fun startDatabaseActionListNodes(actionTask: String,
nodesPaste: List<NodeVersioned>,
newParent: GroupVersioned?,
nodesPaste: List<Node>,
newParent: Group?,
save: Boolean) {
val groupsIdToCopy = ArrayList<PwNodeId<*>>()
val entriesIdToCopy = ArrayList<PwNodeId<UUID>>()
val groupsIdToCopy = ArrayList<NodeId<*>>()
val entriesIdToCopy = ArrayList<NodeId<UUID>>()
nodesPaste.forEach { nodeVersioned ->
when (nodeVersioned.type) {
Type.GROUP -> {
(nodeVersioned as GroupVersioned).nodeId?.let { groupId ->
(nodeVersioned as Group).nodeId?.let { groupId ->
groupsIdToCopy.add(groupId)
}
}
Type.ENTRY -> {
entriesIdToCopy.add((nodeVersioned as EntryVersioned).nodeId)
entriesIdToCopy.add((nodeVersioned as Entry).nodeId)
}
}
}
@@ -325,23 +356,51 @@ class ProgressDialogThread(private val activity: FragmentActivity,
, actionTask)
}
fun startDatabaseCopyNodes(nodesToCopy: List<NodeVersioned>,
newParent: GroupVersioned,
fun startDatabaseCopyNodes(nodesToCopy: List<Node>,
newParent: Group,
save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_COPY_NODES_TASK, nodesToCopy, newParent, save)
}
fun startDatabaseMoveNodes(nodesToMove: List<NodeVersioned>,
newParent: GroupVersioned,
fun startDatabaseMoveNodes(nodesToMove: List<Node>,
newParent: Group,
save: Boolean) {
startDatabaseActionListNodes(ACTION_DATABASE_MOVE_NODES_TASK, nodesToMove, newParent, save)
}
fun startDatabaseDeleteNodes(nodesToDelete: List<NodeVersioned>,
fun startDatabaseDeleteNodes(nodesToDelete: List<Node>,
save: Boolean) {
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
@@ -349,66 +408,80 @@ class ProgressDialogThread(private val activity: FragmentActivity,
*/
fun startDatabaseSaveName(oldName: String,
newName: String) {
newName: String,
save: Boolean) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldName)
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,
newDescription: String) {
newDescription: String,
save: Boolean) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDescription)
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,
newDefaultUsername: String) {
newDefaultUsername: String,
save: Boolean) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDefaultUsername)
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,
newColor: String) {
newColor: String,
save: Boolean) {
start(Bundle().apply {
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldColor)
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,
newCompression: PwCompressionAlgorithm) {
fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm,
save: Boolean) {
start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldCompression)
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,
newMaxHistoryItems: Int) {
newMaxHistoryItems: Int,
save: Boolean) {
start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistoryItems)
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,
newMaxHistorySize: Long) {
newMaxHistorySize: Long,
save: Boolean) {
start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistorySize)
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)
}
/*
@@ -417,48 +490,68 @@ class ProgressDialogThread(private val activity: FragmentActivity,
-------------------
*/
fun startDatabaseSaveEncryption(oldEncryption: PwEncryptionAlgorithm,
newEncryption: PwEncryptionAlgorithm) {
fun startDatabaseSaveEncryption(oldEncryption: EncryptionAlgorithm,
newEncryption: EncryptionAlgorithm,
save: Boolean) {
start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldEncryption)
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,
newKeyDerivation: KdfEngine) {
newKeyDerivation: KdfEngine,
save: Boolean) {
start(Bundle().apply {
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldKeyDerivation)
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,
newIterations: Long) {
newIterations: Long,
save: Boolean) {
start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldIterations)
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,
newMemoryUsage: Long) {
newMemoryUsage: Long,
save: Boolean) {
start(Bundle().apply {
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMemoryUsage)
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,
newParallelism: Int) {
newParallelism: Int,
save: Boolean) {
start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
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

@@ -21,9 +21,8 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
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 java.io.IOException
open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
@@ -38,10 +37,8 @@ open class SaveDatabaseRunnable(protected var context: Context,
if (saveDatabase && result.isSuccess) {
try {
database.saveData(context.contentResolver)
} catch (e: IOException) {
setError(e.message)
} catch (e: DatabaseOutputException) {
setError(e.message)
} catch (e: DatabaseException) {
setError(e)
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
class UpdateCompressionBinariesDatabaseRunnable (
context: Context,
database: Database,
private val oldCompressionAlgorithm: CompressionAlgorithm,
private val newCompressionAlgorithm: CompressionAlgorithm,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
override fun onStartRun() {
// Set new compression
if (database.allowDataCompression) {
try {
database.apply {
updateDataBinaryCompression(oldCompressionAlgorithm, newCompressionAlgorithm)
compressionAlgorithm = newCompressionAlgorithm
}
} catch (e: Exception) {
setError(e)
}
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
if (database.allowDataCompression) {
if (!result.isSuccess) {
try {
database.apply {
compressionAlgorithm = oldCompressionAlgorithm
updateDataBinaryCompression(newCompressionAlgorithm, oldCompressionAlgorithm)
}
} catch (e: Exception) {
setError(e)
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
class DeleteEntryHistoryDatabaseRunnable (
context: Context,
database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
override fun onStartRun() {
try {
mainEntry.removeEntryFromHistory(entryHistoryPosition)
} catch (e: Exception) {
setError(e)
}
super.onStartRun()
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.tasks.ActionRunnable
class RestoreEntryHistoryDatabaseRunnable (
private val context: Context,
private val database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
private val saveDatabase: Boolean)
: ActionRunnable() {
private var updateEntryRunnable: UpdateEntryRunnable? = null
override fun onStartRun() {
try {
val historyToRestore = Entry(mainEntry.getHistory()[entryHistoryPosition])
// Copy history of main entry in the restore entry
mainEntry.getHistory().forEach {
historyToRestore.addEntryToHistory(it)
}
// Update the entry with the fresh formatted entry to restore
updateEntryRunnable = UpdateEntryRunnable(context,
database,
mainEntry,
historyToRestore,
saveDatabase,
null)
updateEntryRunnable?.onStartRun()
} catch (e: Exception) {
setError(e)
}
}
override fun onActionRun() {
updateEntryRunnable?.onActionRun()
}
override fun onFinishRun() {
updateEntryRunnable?.onFinishRun()
}
}

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.node
import android.content.Context

View File

@@ -21,15 +21,15 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
class AddEntryRunnable constructor(
context: Context,
database: Database,
private val mNewEntry: EntryVersioned,
private val mParent: GroupVersioned,
private val mNewEntry: Entry,
private val mParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
@@ -47,8 +47,8 @@ class AddEntryRunnable constructor(
}
}
val oldNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<NodeVersioned>()
val oldNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mNewEntry)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}

View File

@@ -21,14 +21,14 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
class AddGroupRunnable constructor(
context: Context,
database: Database,
private val mNewGroup: GroupVersioned,
private val mParent: GroupVersioned,
private val mNewGroup: Group,
private val mParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
@@ -44,8 +44,8 @@ class AddGroupRunnable constructor(
database.removeGroupFrom(mNewGroup, mParent)
}
val oldNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<NodeVersioned>()
val oldNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mNewGroup)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.action.node
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.tasks.ActionRunnable
/**
@@ -30,7 +30,7 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
* - Move : @param oldNodes empty, @param newNodes NodesToMove
* - Update : @param oldNodes NodesToUpdate, @param newNodes NodesUpdated
*/
class ActionNodesValues(val oldNodes: List<NodeVersioned>, val newNodes: List<NodeVersioned>)
class ActionNodesValues(val oldNodes: List<Node>, val newNodes: List<Node>)
abstract class AfterActionNodesFinish {
abstract fun onActionNodesFinish(result: ActionRunnable.Result, actionNodesValues: ActionNodesValues)

View File

@@ -22,19 +22,21 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.CopyDatabaseEntryException
import com.kunzisoft.keepass.database.exception.CopyDatabaseGroupException
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.CopyEntryDatabaseException
import com.kunzisoft.keepass.database.exception.CopyGroupDatabaseException
class CopyNodesRunnable constructor(
context: Context,
database: Database,
private val mNodesToCopy: List<NodeVersioned>,
private val mNewParent: GroupVersioned,
private val mNodesToCopy: List<Node>,
private val mNewParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
private var mEntriesCopied = ArrayList<EntryVersioned>()
private var mEntriesCopied = ArrayList<Entry>()
override fun nodeAction() {
@@ -42,7 +44,7 @@ class CopyNodesRunnable constructor(
when (currentNode.type) {
Type.GROUP -> {
Log.e(TAG, "Copy not allowed for group")// Only finish thread
setError(CopyDatabaseGroupException())
setError(CopyGroupDatabaseException())
break@foreachNode
}
Type.ENTRY -> {
@@ -51,18 +53,18 @@ class CopyNodesRunnable constructor(
// Update entry with new values
mNewParent.touch(modified = false, touchParents = true)
val entryCopied = database.copyEntryTo(currentNode as EntryVersioned, mNewParent)
val entryCopied = database.copyEntryTo(currentNode as Entry, mNewParent)
if (entryCopied != null) {
entryCopied.touch(modified = true, touchParents = true)
mEntriesCopied.add(entryCopied)
} else {
Log.e(TAG, "Unable to create a copy of the entry")
setError(CopyDatabaseEntryException())
setError(CopyEntryDatabaseException())
break@foreachNode
}
} else {
// Only finish thread
setError(CopyDatabaseEntryException())
setError(CopyEntryDatabaseException())
break@foreachNode
}
}

View File

@@ -21,18 +21,20 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
class DeleteNodesRunnable(context: Context,
database: Database,
private val mNodesToDelete: List<NodeVersioned>,
private val mNodesToDelete: List<Node>,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
private var mParent: GroupVersioned? = null
private var mParent: Group? = null
private var mCanRecycle: Boolean = false
private var mNodesToDeleteBackup = ArrayList<NodeVersioned>()
private var mNodesToDeleteBackup = ArrayList<Node>()
override fun nodeAction() {
@@ -43,7 +45,7 @@ class DeleteNodesRunnable(context: Context,
when (currentNode.type) {
Type.GROUP -> {
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(GroupVersioned(currentNode as GroupVersioned))
mNodesToDeleteBackup.add(Group(currentNode as Group))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
if (mCanRecycle) {
@@ -54,7 +56,7 @@ class DeleteNodesRunnable(context: Context,
}
Type.ENTRY -> {
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(EntryVersioned(currentNode as EntryVersioned))
mNodesToDeleteBackup.add(Entry(currentNode as Entry))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
if (mCanRecycle) {
@@ -74,10 +76,10 @@ class DeleteNodesRunnable(context: Context,
mNodesToDeleteBackup.forEach { backupNode ->
when (backupNode.type) {
Type.GROUP -> {
database.undoRecycle(backupNode as GroupVersioned, it)
database.undoRecycle(backupNode as Group, it)
}
Type.ENTRY -> {
database.undoRecycle(backupNode as EntryVersioned, it)
database.undoRecycle(backupNode as Entry, it)
}
}
}

View File

@@ -22,20 +22,21 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.MoveDatabaseEntryException
import com.kunzisoft.keepass.database.exception.MoveDatabaseGroupException
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.EntryDatabaseException
import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException
class MoveNodesRunnable constructor(
context: Context,
database: Database,
private val mNodesToMove: List<NodeVersioned>,
private val mNewParent: GroupVersioned,
private val mNodesToMove: List<Node>,
private val mNewParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
private var mOldParent: GroupVersioned? = null
private var mOldParent: Group? = null
override fun nodeAction() {
@@ -45,7 +46,7 @@ class MoveNodesRunnable constructor(
when (nodeToMove.type) {
Type.GROUP -> {
val groupToMove = nodeToMove as GroupVersioned
val groupToMove = nodeToMove as Group
// Move group in new parent if not in the current group
if (groupToMove != mNewParent
&& !mNewParent.isContainedIn(groupToMove)) {
@@ -53,12 +54,12 @@ class MoveNodesRunnable constructor(
database.moveGroupTo(groupToMove, mNewParent)
} else {
// Only finish thread
setError(MoveDatabaseGroupException())
setError(MoveGroupDatabaseException())
break@foreachNode
}
}
Type.ENTRY -> {
val entryToMove = nodeToMove as EntryVersioned
val entryToMove = nodeToMove as Entry
// Move only if the parent change
if (mOldParent != mNewParent
// and root can contains entry
@@ -67,7 +68,7 @@ class MoveNodesRunnable constructor(
database.moveEntryTo(entryToMove, mNewParent)
} else {
// Only finish thread
setError(MoveDatabaseEntryException())
setError(EntryDatabaseException())
break@foreachNode
}
}
@@ -83,8 +84,8 @@ class MoveNodesRunnable constructor(
if (mOldParent != null &&
mOldParent != nodeToMove.parent) {
when (nodeToMove.type) {
Type.GROUP -> database.moveGroupTo(nodeToMove as GroupVersioned, mOldParent!!)
Type.ENTRY -> database.moveEntryTo(nodeToMove as EntryVersioned, mOldParent!!)
Type.GROUP -> database.moveGroupTo(nodeToMove as Group, mOldParent!!)
Type.ENTRY -> database.moveEntryTo(nodeToMove as Entry, mOldParent!!)
}
}
}

View File

@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database
package com.kunzisoft.keepass.database.action.node
/** "Delegate" class for operating on each group when traversing all of

View File

@@ -21,20 +21,20 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
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.Entry
import com.kunzisoft.keepass.database.element.node.Node
class UpdateEntryRunnable constructor(
context: Context,
database: Database,
private val mOldEntry: EntryVersioned,
private val mNewEntry: EntryVersioned,
private val mOldEntry: Entry,
private val mNewEntry: Entry,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
// Keep backup of original values in case save fails
private var mBackupEntryHistory: EntryVersioned = EntryVersioned(mOldEntry)
private var mBackupEntryHistory: Entry = Entry(mOldEntry)
override fun nodeAction() {
// WARNING : Re attribute parent removed in entry edit activity to save memory
@@ -45,8 +45,8 @@ class UpdateEntryRunnable constructor(
mNewEntry.touch(modified = true, touchParents = true)
// Create an entry history (an entry history don't have history)
mOldEntry.addEntryToHistory(EntryVersioned(mBackupEntryHistory, copyHistory = false))
database.removeOldestHistory(mOldEntry)
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
database.removeOldestEntryHistory(mOldEntry)
// Only change data in index
database.updateEntry(mOldEntry)
@@ -59,9 +59,9 @@ class UpdateEntryRunnable constructor(
database.updateEntry(mOldEntry)
}
val oldNodesReturn = ArrayList<NodeVersioned>()
val oldNodesReturn = ArrayList<Node>()
oldNodesReturn.add(mBackupEntryHistory)
val newNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mOldEntry)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}

View File

@@ -21,20 +21,20 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
class UpdateGroupRunnable constructor(
context: Context,
database: Database,
private val mOldGroup: GroupVersioned,
private val mNewGroup: GroupVersioned,
private val mOldGroup: Group,
private val mNewGroup: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
// Keep backup of original values in case save fails
private val mBackupGroup: GroupVersioned = GroupVersioned(mOldGroup)
private val mBackupGroup: Group = Group(mOldGroup)
override fun nodeAction() {
// WARNING : Re attribute parent and children removed in group activity to save memory
@@ -56,9 +56,9 @@ class UpdateGroupRunnable constructor(
database.updateGroup(mOldGroup)
}
val oldNodesReturn = ArrayList<NodeVersioned>()
val oldNodesReturn = ArrayList<Node>()
oldNodesReturn.add(mBackupGroup)
val newNodesReturn = ArrayList<NodeVersioned>()
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mOldGroup)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}

View File

@@ -1,12 +1,33 @@
/*
* 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.cursor
import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.PwEntry
import com.kunzisoft.keepass.database.element.PwIconFactory
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId
import java.util.*
abstract class EntryCursor<EntryId, PwEntryV : PwEntry<*, EntryId, *, *>> : MatrixCursor(arrayOf(
abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>> : MatrixCursor(arrayOf(
_ID,
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
@@ -17,16 +38,18 @@ abstract class EntryCursor<EntryId, PwEntryV : PwEntry<*, EntryId, *, *>> : Matr
COLUMN_INDEX_USERNAME,
COLUMN_INDEX_PASSWORD,
COLUMN_INDEX_URL,
COLUMN_INDEX_NOTES
COLUMN_INDEX_NOTES,
COLUMN_INDEX_EXPIRY_TIME,
COLUMN_INDEX_EXPIRES
)) {
protected var entryId: Long = 0
abstract fun addEntry(entry: PwEntryV)
abstract fun getPwNodeId(): PwNodeId<EntryId>
abstract fun getPwNodeId(): NodeId<EntryId>
open fun populateEntry(pwEntry: PwEntryV, iconFactory: PwIconFactory) {
open fun populateEntry(pwEntry: PwEntryV, iconFactory: IconImageFactory) {
pwEntry.nodeId = getPwNodeId()
pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE))
@@ -37,6 +60,9 @@ abstract class EntryCursor<EntryId, PwEntryV : PwEntry<*, EntryId, *, *>> : Matr
pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD))
pwEntry.url = getString(getColumnIndex(COLUMN_INDEX_URL))
pwEntry.notes = getString(getColumnIndex(COLUMN_INDEX_NOTES))
pwEntry.expiryTime = DateInstant(getString(getColumnIndex(COLUMN_INDEX_EXPIRY_TIME)))
pwEntry.expires = getString(getColumnIndex(COLUMN_INDEX_EXPIRES))
.toLowerCase(Locale.ENGLISH) != "false"
}
companion object {
@@ -51,5 +77,7 @@ abstract class EntryCursor<EntryId, PwEntryV : PwEntry<*, EntryId, *, *>> : Matr
const val COLUMN_INDEX_PASSWORD = "password"
const val COLUMN_INDEX_URL = "URL"
const val COLUMN_INDEX_NOTES = "notes"
const val COLUMN_INDEX_EXPIRY_TIME = "expiry_time"
const val COLUMN_INDEX_EXPIRES = "expires"
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.cursor
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDB
class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
override fun addEntry(entry: EntryKDB) {
addRow(arrayOf(
entryId,
entry.id.mostSignificantBits,
entry.id.leastSignificantBits,
entry.title,
entry.icon.iconId,
DatabaseVersioned.UUID_ZERO.mostSignificantBits,
DatabaseVersioned.UUID_ZERO.leastSignificantBits,
entry.username,
entry.password,
entry.url,
entry.notes,
entry.expiryTime,
entry.expires
))
entryId++
}
}

View File

@@ -1,15 +1,34 @@
/*
* 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.cursor
import com.kunzisoft.keepass.database.element.PwEntryV4
import com.kunzisoft.keepass.database.element.PwIconFactory
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import java.util.UUID
class EntryCursorV4 : EntryCursorUUID<PwEntryV4>() {
class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
private val extraFieldCursor: ExtraFieldCursor = ExtraFieldCursor()
override fun addEntry(entry: PwEntryV4) {
override fun addEntry(entry: EntryKDBX) {
addRow(arrayOf(
entryId,
entry.id.mostSignificantBits,
@@ -21,7 +40,9 @@ class EntryCursorV4 : EntryCursorUUID<PwEntryV4>() {
entry.username,
entry.password,
entry.url,
entry.notes
entry.notes,
entry.expiryTime,
entry.expires
))
for (element in entry.customFields.entries) {
@@ -31,7 +52,7 @@ class EntryCursorV4 : EntryCursorUUID<PwEntryV4>() {
entryId++
}
override fun populateEntry(pwEntry: PwEntryV4, iconFactory: PwIconFactory) {
override fun populateEntry(pwEntry: EntryKDBX, iconFactory: IconImageFactory) {
super.populateEntry(pwEntry, iconFactory)
// Retrieve custom icon

View File

@@ -1,14 +1,33 @@
/*
* 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.cursor
import com.kunzisoft.keepass.database.element.PwEntry
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.database.element.PwNodeIdUUID
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import java.util.*
abstract class EntryCursorUUID<EntryV: PwEntry<*, UUID, *, *>>: EntryCursor<UUID, EntryV>() {
abstract class EntryCursorUUID<EntryV: EntryVersioned<*, UUID, *, *>>: EntryCursor<UUID, EntryV>() {
override fun getPwNodeId(): PwNodeId<UUID> {
return PwNodeIdUUID(
override fun getPwNodeId(): NodeId<UUID> {
return NodeIdUUID(
UUID(getLong(getColumnIndex(COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))))
}

View File

@@ -1,25 +0,0 @@
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.PwDatabase
import com.kunzisoft.keepass.database.element.PwEntryV3
class EntryCursorV3 : EntryCursorUUID<PwEntryV3>() {
override fun addEntry(entry: PwEntryV3) {
addRow(arrayOf(
entryId,
entry.id.mostSignificantBits,
entry.id.leastSignificantBits,
entry.title,
entry.icon.iconId,
PwDatabase.UUID_ZERO.mostSignificantBits,
PwDatabase.UUID_ZERO.leastSignificantBits,
entry.username,
entry.password,
entry.url,
entry.notes
))
entryId++
}
}

View File

@@ -1,9 +1,28 @@
/*
* 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.cursor
import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.PwEntryV4
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.security.ProtectedString
class ExtraFieldCursor : MatrixCursor(arrayOf(
@@ -22,7 +41,7 @@ class ExtraFieldCursor : MatrixCursor(arrayOf(
fieldId++
}
fun populateExtraFieldInEntry(pwEntry: PwEntryV4) {
fun populateExtraFieldInEntry(pwEntry: EntryKDBX) {
pwEntry.putExtraField(getString(getColumnIndex(COLUMN_LABEL)),
ProtectedString(getInt(getColumnIndex(COLUMN_PROTECTION)) > 0,
getString(getColumnIndex(COLUMN_VALUE))))

View File

@@ -0,0 +1,150 @@
/*
* 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.element
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ConfigurationCompat
import java.text.SimpleDateFormat
import java.util.*
class DateInstant : Parcelable {
private var jDate: Date = Date()
val date: Date
get() = jDate
constructor(source: DateInstant) {
this.jDate = Date(source.jDate.time)
}
constructor(date: Date) {
jDate = Date(date.time)
}
constructor(millis: Long) {
jDate = Date(millis)
}
constructor(string: String) {
jDate = dateFormat.parse(string)
}
constructor() {
jDate = Date()
}
protected constructor(parcel: Parcel) {
jDate = parcel.readSerializable() as Date
}
override fun describeContents(): Int {
return 0
}
fun getDateTimeString(resources: Resources): String {
return Companion.getDateTimeString(resources, this.date)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(date)
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null) {
return false
}
if (javaClass != other.javaClass) {
return false
}
val date = other as DateInstant
return isSameDate(jDate, date.jDate)
}
override fun hashCode(): Int {
return jDate.hashCode()
}
override fun toString(): String {
return dateFormat.format(jDate)
}
companion object {
val NEVER_EXPIRE = neverExpire
private val dateFormat = SimpleDateFormat.getDateTimeInstance()
private val neverExpire: DateInstant
get() {
val cal = Calendar.getInstance()
cal.set(Calendar.YEAR, 2999)
cal.set(Calendar.MONTH, 11)
cal.set(Calendar.DAY_OF_MONTH, 28)
cal.set(Calendar.HOUR, 23)
cal.set(Calendar.MINUTE, 59)
cal.set(Calendar.SECOND, 59)
return DateInstant(cal.time)
}
@JvmField
val CREATOR: Parcelable.Creator<DateInstant> = object : Parcelable.Creator<DateInstant> {
override fun createFromParcel(parcel: Parcel): DateInstant {
return DateInstant(parcel)
}
override fun newArray(size: Int): Array<DateInstant?> {
return arrayOfNulls(size)
}
}
private fun isSameDate(d1: Date?, d2: Date?): Boolean {
val cal1 = Calendar.getInstance()
cal1.time = d1
cal1.set(Calendar.MILLISECOND, 0)
val cal2 = Calendar.getInstance()
cal2.time = d2
cal2.set(Calendar.MILLISECOND, 0)
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) &&
cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH) &&
cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) &&
cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) &&
cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND)
}
fun getDateTimeString(resources: Resources, date: Date): String {
return java.text.DateFormat.getDateTimeInstance(
java.text.DateFormat.MEDIUM,
java.text.DateFormat.MEDIUM,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(date)
}
}
}

View File

@@ -19,12 +19,13 @@
*/
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.Date
import java.util.UUID
class PwDeletedObject {
class DeletedObject {
var uuid: UUID = PwDatabase.UUID_ZERO
var uuid: UUID = DatabaseVersioned.UUID_ZERO
var deletionTime: Date? = null
get() = if (field == null) {
Date(System.currentTimeMillis())
@@ -43,7 +44,7 @@ class PwDeletedObject {
return true
if (other == null)
return false
if (other !is PwDeletedObject)
if (other !is DeletedObject)
return false
return uuid == other.uuid
}

View File

@@ -0,0 +1,442 @@
/*
* 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.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import java.util.*
import kotlin.collections.ArrayList
class Entry : Node, EntryVersionedInterface<Group> {
var entryKDB: EntryKDB? = null
private set
var entryKDBX: EntryKDBX? = null
private set
fun updateWith(entry: Entry, copyHistory: Boolean = true) {
entry.entryKDB?.let {
this.entryKDB?.updateWith(it)
}
entry.entryKDBX?.let {
this.entryKDBX?.updateWith(it, copyHistory)
}
}
/**
* Use this constructor to copy an Entry with exact same values
*/
constructor(entry: Entry, copyHistory: Boolean = true) {
if (entry.entryKDB != null) {
this.entryKDB = EntryKDB()
}
if (entry.entryKDBX != null) {
this.entryKDBX = EntryKDBX()
}
updateWith(entry, copyHistory)
}
constructor(entry: EntryKDB) {
this.entryKDBX = null
this.entryKDB = entry
}
constructor(entry: EntryKDBX) {
this.entryKDB = null
this.entryKDBX = entry
}
constructor(parcel: Parcel) {
entryKDB = parcel.readParcelable(EntryKDB::class.java.classLoader)
entryKDBX = parcel.readParcelable(EntryKDBX::class.java.classLoader)
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(entryKDB, flags)
dest.writeParcelable(entryKDBX, flags)
}
override var nodeId: NodeId<UUID>
get() = entryKDBX?.nodeId ?: entryKDB?.nodeId ?: NodeIdUUID()
set(value) {
entryKDB?.nodeId = value
entryKDBX?.nodeId = value
}
override var title: String
get() = entryKDB?.title ?: entryKDBX?.title ?: ""
set(value) {
entryKDB?.title = value
entryKDBX?.title = value
}
override var icon: IconImage
get() {
return entryKDB?.icon ?: entryKDBX?.icon ?: IconImageStandard()
}
set(value) {
entryKDB?.icon = value
entryKDBX?.icon = value
}
override val type: Type
get() = Type.ENTRY
override var parent: Group?
get() {
entryKDB?.parent?.let {
return Group(it)
}
entryKDBX?.parent?.let {
return Group(it)
}
return null
}
set(value) {
entryKDB?.parent = value?.groupKDB
entryKDBX?.parent = value?.groupKDBX
}
override fun containsParent(): Boolean {
return entryKDB?.containsParent() ?: entryKDBX?.containsParent() ?: false
}
override fun afterAssignNewParent() {
entryKDBX?.afterChangeParent()
}
override fun touch(modified: Boolean, touchParents: Boolean) {
entryKDB?.touch(modified, touchParents)
entryKDBX?.touch(modified, touchParents)
}
override fun isContainedIn(container: Group): Boolean {
var contained: Boolean? = false
container.groupKDB?.let {
contained = entryKDB?.isContainedIn(it)
}
container.groupKDBX?.let {
contained = entryKDBX?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: DateInstant
get() = entryKDB?.creationTime ?: entryKDBX?.creationTime ?: DateInstant()
set(value) {
entryKDB?.creationTime = value
entryKDBX?.creationTime = value
}
override var lastModificationTime: DateInstant
get() = entryKDB?.lastModificationTime ?: entryKDBX?.lastModificationTime ?: DateInstant()
set(value) {
entryKDB?.lastModificationTime = value
entryKDBX?.lastModificationTime = value
}
override var lastAccessTime: DateInstant
get() = entryKDB?.lastAccessTime ?: entryKDBX?.lastAccessTime ?: DateInstant()
set(value) {
entryKDB?.lastAccessTime = value
entryKDBX?.lastAccessTime = value
}
override var expiryTime: DateInstant
get() = entryKDB?.expiryTime ?: entryKDBX?.expiryTime ?: DateInstant()
set(value) {
entryKDB?.expiryTime = value
entryKDBX?.expiryTime = value
}
override var expires: Boolean
get() = entryKDB?.expires ?: entryKDBX?.expires ?: false
set(value) {
entryKDB?.expires = value
entryKDBX?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = entryKDB?.isCurrentlyExpires ?: entryKDBX?.isCurrentlyExpires ?: false
override var username: String
get() = entryKDB?.username ?: entryKDBX?.username ?: ""
set(value) {
entryKDB?.username = value
entryKDBX?.username = value
}
override var password: String
get() = entryKDB?.password ?: entryKDBX?.password ?: ""
set(value) {
entryKDB?.password = value
entryKDBX?.password = value
}
override var url: String
get() = entryKDB?.url ?: entryKDBX?.url ?: ""
set(value) {
entryKDB?.url = value
entryKDBX?.url = value
}
override var notes: String
get() = entryKDB?.notes ?: entryKDBX?.notes ?: ""
set(value) {
entryKDB?.notes = value
entryKDBX?.notes = value
}
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(): String {
return if (isTan()) {
"$PMS_TAN_ENTRY $username"
} else {
if (title.isEmpty())
if (username.isEmpty())
if (url.isEmpty())
nodeId.toString()
else
url
else
username
else
title
}
}
/*
------------
KDB Methods
------------
*/
/**
* If it's a node with only meta information like Meta-info SYSTEM Database Color
* @return false by default, true if it's a meta stream
*/
val isMetaStream: Boolean
get() = entryKDB?.isMetaStream ?: false
/*
------------
KDBX Methods
------------
*/
var iconCustom: IconImageCustom
get() = entryKDBX?.iconCustom ?: IconImageCustom.UNKNOWN_ICON
set(value) {
entryKDBX?.iconCustom = value
}
/**
* Retrieve custom fields to show, key is the label, value is the value of field (protected or not)
* @return Map of label/value
*/
val customFields: HashMap<String, ProtectedString>
get() = entryKDBX?.customFields ?: HashMap()
/**
* To redefine if version of entry allow custom field,
* @return true if entry allows custom field
*/
fun allowCustomFields(): Boolean {
return entryKDBX?.allowCustomFields() ?: false
}
fun removeAllFields() {
entryKDBX?.removeAllFields()
}
/**
* Update or add an extra field to the list (standard or custom)
* @param label Label of field, must be unique
* @param value Value of field
*/
fun putExtraField(label: String, value: ProtectedString) {
entryKDBX?.putExtraField(label, value)
}
fun getOtpElement(): OtpElement? {
return OtpEntryFields.parseFields { key ->
customFields[key]?.toString()
}
}
fun startToManageFieldReferences(db: DatabaseKDBX) {
entryKDBX?.startToManageFieldReferences(db)
}
fun stopToManageFieldReferences() {
entryKDBX?.stopToManageFieldReferences()
}
fun getAttachments(): ArrayList<EntryAttachment> {
val attachments = ArrayList<EntryAttachment>()
val binaryDescriptionKDB = entryKDB?.binaryDescription ?: ""
val binaryKDB = entryKDB?.binaryData
if (binaryKDB != null) {
attachments.add(EntryAttachment(binaryDescriptionKDB, binaryKDB))
}
val actionEach = object : (Map.Entry<String, BinaryAttachment>)->Unit {
override fun invoke(mapEntry: Map.Entry<String, BinaryAttachment>) {
attachments.add(EntryAttachment(mapEntry.key, mapEntry.value))
}
}
entryKDBX?.binaries?.forEach(actionEach)
return attachments
}
fun getHistory(): ArrayList<Entry> {
val history = ArrayList<Entry>()
val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
for (entryHistory in entryKDBXHistory) {
history.add(Entry(entryHistory))
}
return history
}
fun addEntryToHistory(entry: Entry) {
entry.entryKDBX?.let {
entryKDBX?.addEntryToHistory(it)
}
}
fun removeEntryFromHistory(position: Int) {
entryKDBX?.removeEntryFromHistory(position)
}
fun removeAllHistory() {
entryKDBX?.removeAllHistory()
}
fun removeOldestEntryFromHistory() {
entryKDBX?.removeOldestEntryFromHistory()
}
fun getSize(): Long {
return entryKDBX?.size ?: 0L
}
fun containsCustomData(): Boolean {
return entryKDBX?.containsCustomData() ?: false
}
/*
------------
Converter
------------
*/
/**
* Retrieve generated entry info,
* Remove parameter fields and add auto generated elements in auto custom fields
*/
fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo {
val entryInfo = EntryInfo()
if (raw)
database?.stopManageEntry(this)
else
database?.startManageEntry(this)
entryInfo.id = nodeId.toString()
entryInfo.title = title
entryInfo.username = username
entryInfo.password = password
entryInfo.url = url
entryInfo.notes = notes
for (entry in customFields.entries) {
entryInfo.customFields.add(
Field(entry.key, entry.value))
}
// Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel
// Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
if (!raw)
database?.stopManageEntry(this)
return entryInfo
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Entry
if (entryKDB != other.entryKDB) return false
if (entryKDBX != other.entryKDBX) return false
return true
}
override fun hashCode(): Int {
var result = entryKDB?.hashCode() ?: 0
result = 31 * result + (entryKDBX?.hashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
const val PMS_TAN_ENTRY = "<TAN>"
}
}

View File

@@ -1,395 +0,0 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import java.util.*
import kotlin.collections.ArrayList
class EntryVersioned : NodeVersioned, PwEntryInterface<GroupVersioned> {
var pwEntryV3: PwEntryV3? = null
private set
var pwEntryV4: PwEntryV4? = null
private set
fun updateWith(entry: EntryVersioned, copyHistory: Boolean = true) {
entry.pwEntryV3?.let {
this.pwEntryV3?.updateWith(it)
}
entry.pwEntryV4?.let {
this.pwEntryV4?.updateWith(it, copyHistory)
}
}
/**
* Use this constructor to copy an Entry with exact same values
*/
constructor(entry: EntryVersioned, copyHistory: Boolean = true) {
if (entry.pwEntryV3 != null) {
this.pwEntryV3 = PwEntryV3()
}
if (entry.pwEntryV4 != null) {
this.pwEntryV4 = PwEntryV4()
}
updateWith(entry, copyHistory)
}
constructor(entry: PwEntryV3) {
this.pwEntryV4 = null
this.pwEntryV3 = entry
}
constructor(entry: PwEntryV4) {
this.pwEntryV3 = null
this.pwEntryV4 = entry
}
constructor(parcel: Parcel) {
pwEntryV3 = parcel.readParcelable(PwEntryV3::class.java.classLoader)
pwEntryV4 = parcel.readParcelable(PwEntryV4::class.java.classLoader)
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(pwEntryV3, flags)
dest.writeParcelable(pwEntryV4, flags)
}
override var nodeId: PwNodeId<UUID>
get() = pwEntryV4?.nodeId ?: pwEntryV3?.nodeId ?: PwNodeIdUUID()
set(value) {
pwEntryV3?.nodeId = value
pwEntryV4?.nodeId = value
}
override var title: String
get() = pwEntryV3?.title ?: pwEntryV4?.title ?: ""
set(value) {
pwEntryV3?.title = value
pwEntryV4?.title = value
}
override var icon: PwIcon
get() {
return pwEntryV3?.icon ?: pwEntryV4?.icon ?: PwIconStandard()
}
set(value) {
pwEntryV3?.icon = value
pwEntryV4?.icon = value
}
override val type: Type
get() = Type.ENTRY
override var parent: GroupVersioned?
get() {
pwEntryV3?.parent?.let {
return GroupVersioned(it)
}
pwEntryV4?.parent?.let {
return GroupVersioned(it)
}
return null
}
set(value) {
pwEntryV3?.parent = value?.pwGroupV3
pwEntryV4?.parent = value?.pwGroupV4
}
override fun containsParent(): Boolean {
return pwEntryV3?.containsParent() ?: pwEntryV4?.containsParent() ?: false
}
override fun afterAssignNewParent() {
pwEntryV4?.afterChangeParent()
}
override fun touch(modified: Boolean, touchParents: Boolean) {
pwEntryV3?.touch(modified, touchParents)
pwEntryV4?.touch(modified, touchParents)
}
override fun isContainedIn(container: GroupVersioned): Boolean {
var contained: Boolean? = false
container.pwGroupV3?.let {
contained = pwEntryV3?.isContainedIn(it)
}
container.pwGroupV4?.let {
contained = pwEntryV4?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: PwDate
get() = pwEntryV3?.creationTime ?: pwEntryV4?.creationTime ?: PwDate()
set(value) {
pwEntryV3?.creationTime = value
pwEntryV4?.creationTime = value
}
override var lastModificationTime: PwDate
get() = pwEntryV3?.lastModificationTime ?: pwEntryV4?.lastModificationTime ?: PwDate()
set(value) {
pwEntryV3?.lastModificationTime = value
pwEntryV4?.lastModificationTime = value
}
override var lastAccessTime: PwDate
get() = pwEntryV3?.lastAccessTime ?: pwEntryV4?.lastAccessTime ?: PwDate()
set(value) {
pwEntryV3?.lastAccessTime = value
pwEntryV4?.lastAccessTime = value
}
override var expiryTime: PwDate
get() = pwEntryV3?.expiryTime ?: pwEntryV4?.expiryTime ?: PwDate()
set(value) {
pwEntryV3?.expiryTime = value
pwEntryV4?.expiryTime = value
}
override var expires: Boolean
get() = pwEntryV3?.expires ?: pwEntryV4?.expires ?: false
set(value) {
pwEntryV3?.expires = value
pwEntryV4?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = pwEntryV3?.isCurrentlyExpires ?: pwEntryV4?.isCurrentlyExpires ?: false
override var username: String
get() = pwEntryV3?.username ?: pwEntryV4?.username ?: ""
set(value) {
pwEntryV3?.username = value
pwEntryV4?.username = value
}
override var password: String
get() = pwEntryV3?.password ?: pwEntryV4?.password ?: ""
set(value) {
pwEntryV3?.password = value
pwEntryV4?.password = value
}
override var url: String
get() = pwEntryV3?.url ?: pwEntryV4?.url ?: ""
set(value) {
pwEntryV3?.url = value
pwEntryV4?.url = value
}
override var notes: String
get() = pwEntryV3?.notes ?: pwEntryV4?.notes ?: ""
set(value) {
pwEntryV3?.notes = value
pwEntryV4?.notes = value
}
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
fun getVisualTitle(): String {
return getVisualTitle(isTan(),
title,
username,
url,
nodeId.toString())
}
/*
------------
V3 Methods
------------
*/
/**
* If it's a node with only meta information like Meta-info SYSTEM Database Color
* @return false by default, true if it's a meta stream
*/
val isMetaStream: Boolean
get() = pwEntryV3?.isMetaStream ?: false
/*
------------
V4 Methods
------------
*/
var iconCustom: PwIconCustom
get() = pwEntryV4?.iconCustom ?: PwIconCustom.UNKNOWN_ICON
set(value) {
pwEntryV4?.iconCustom = value
}
/**
* Retrieve custom fields to show, key is the label, value is the value of field (protected or not)
* @return Map of label/value
*/
val customFields: HashMap<String, ProtectedString>
get() = pwEntryV4?.customFields ?: HashMap()
/**
* To redefine if version of entry allow custom field,
* @return true if entry allows custom field
*/
fun allowCustomFields(): Boolean {
return pwEntryV4?.allowCustomFields() ?: false
}
fun removeAllFields() {
pwEntryV4?.removeAllFields()
}
/**
* Update or add an extra field to the list (standard or custom)
* @param label Label of field, must be unique
* @param value Value of field
*/
fun putExtraField(label: String, value: ProtectedString) {
pwEntryV4?.putExtraField(label, value)
}
fun getOtpElement(): OtpElement? {
return OtpEntryFields.parseFields { key ->
customFields[key]?.toString()
}
}
fun startToManageFieldReferences(db: PwDatabaseV4) {
pwEntryV4?.startToManageFieldReferences(db)
}
fun stopToManageFieldReferences() {
pwEntryV4?.stopToManageFieldReferences()
}
fun getHistory(): ArrayList<EntryVersioned> {
val history = ArrayList<EntryVersioned>()
val entryV4History = pwEntryV4?.history ?: ArrayList()
for (entryHistory in entryV4History) {
history.add(EntryVersioned(entryHistory))
}
return history
}
fun addEntryToHistory(entry: EntryVersioned) {
entry.pwEntryV4?.let {
pwEntryV4?.addEntryToHistory(it)
}
}
fun removeAllHistory() {
pwEntryV4?.removeAllHistory()
}
fun removeOldestEntryFromHistory() {
pwEntryV4?.removeOldestEntryFromHistory()
}
fun getSize(): Long {
return pwEntryV4?.size ?: 0L
}
fun containsCustomData(): Boolean {
return pwEntryV4?.containsCustomData() ?: false
}
/*
------------
Converter
------------
*/
/**
* Retrieve generated entry info,
* Remove parameter fields and add auto generated elements in auto custom fields
*/
fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo {
val entryInfo = EntryInfo()
if (raw)
database?.stopManageEntry(this)
else
database?.startManageEntry(this)
entryInfo.id = nodeId.toString()
entryInfo.title = title
entryInfo.username = username
entryInfo.password = password
entryInfo.url = url
entryInfo.notes = notes
for (entry in customFields.entries) {
entryInfo.customFields.add(
Field(entry.key, entry.value))
}
// Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel
// Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
if (!raw)
database?.stopManageEntry(this)
return entryInfo
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as EntryVersioned
if (pwEntryV3 != other.pwEntryV3) return false
if (pwEntryV4 != other.pwEntryV4) return false
return true
}
override fun hashCode(): Int {
var result = pwEntryV3?.hashCode() ?: 0
result = 31 * result + (pwEntryV4?.hashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<EntryVersioned> {
override fun createFromParcel(parcel: Parcel): EntryVersioned {
return EntryVersioned(parcel)
}
override fun newArray(size: Int): Array<EntryVersioned?> {
return arrayOfNulls(size)
}
const val PMS_TAN_ENTRY = "<TAN>"
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(isTan: Boolean, title: String, userName: String, url: String, id: String): String {
return if (isTan) {
"$PMS_TAN_ENTRY $userName"
} else {
if (title.isEmpty())
if (userName.isEmpty())
if (url.isEmpty())
id
else
url
else
userName
else
title
}
}
}
}

View File

@@ -0,0 +1,400 @@
/*
* 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.element
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
import kotlin.collections.ArrayList
class Group : Node, GroupVersionedInterface<Group, Entry> {
var groupKDB: GroupKDB? = null
private set
var groupKDBX: GroupKDBX? = null
private set
fun updateWith(group: Group) {
group.groupKDB?.let {
this.groupKDB?.updateWith(it)
}
group.groupKDBX?.let {
this.groupKDBX?.updateWith(it)
}
}
/**
* Use this constructor to copy a Group
*/
constructor(group: Group) {
if (group.groupKDB != null) {
if (this.groupKDB == null)
this.groupKDB = GroupKDB()
}
if (group.groupKDBX != null) {
if (this.groupKDBX == null)
this.groupKDBX = GroupKDBX()
}
updateWith(group)
}
constructor(group: GroupKDB) {
this.groupKDBX = null
this.groupKDB = group
}
constructor(group: GroupKDBX) {
this.groupKDB = null
this.groupKDBX = group
}
constructor(parcel: Parcel) {
groupKDB = parcel.readParcelable(GroupKDB::class.java.classLoader)
groupKDBX = parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
enum class ChildFilter {
META_STREAM, EXPIRED;
companion object {
fun getDefaults(context: Context): Array<ChildFilter> {
return if (PreferencesUtil.showExpiredEntries(context)) {
arrayOf(META_STREAM)
} else {
arrayOf(META_STREAM, EXPIRED)
}
}
}
}
companion object CREATOR : Parcelable.Creator<Group> {
override fun createFromParcel(parcel: Parcel): Group {
return Group(parcel)
}
override fun newArray(size: Int): Array<Group?> {
return arrayOfNulls(size)
}
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(groupKDB, flags)
dest.writeParcelable(groupKDBX, flags)
}
override val nodeId: NodeId<*>?
get() = groupKDBX?.nodeId ?: groupKDB?.nodeId
override var title: String
get() = groupKDB?.title ?: groupKDBX?.title ?: ""
set(value) {
groupKDB?.title = value
groupKDBX?.title = value
}
override var icon: IconImage
get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImageStandard()
set(value) {
groupKDB?.icon = value
groupKDBX?.icon = value
}
override val type: Type
get() = Type.GROUP
override var parent: Group?
get() {
groupKDB?.parent?.let {
return Group(it)
}
groupKDBX?.parent?.let {
return Group(it)
}
return null
}
set(value) {
groupKDB?.parent = value?.groupKDB
groupKDBX?.parent = value?.groupKDBX
}
override fun containsParent(): Boolean {
return groupKDB?.containsParent() ?: groupKDBX?.containsParent() ?: false
}
override fun afterAssignNewParent() {
groupKDB?.afterAssignNewParent()
groupKDBX?.afterAssignNewParent()
}
fun addChildrenFrom(group: Group) {
group.groupKDB?.getChildEntries()?.forEach { entryToAdd ->
groupKDB?.addChildEntry(entryToAdd)
}
group.groupKDB?.getChildGroups()?.forEach { groupToAdd ->
groupKDB?.addChildGroup(groupToAdd)
}
group.groupKDBX?.getChildEntries()?.forEach { entryToAdd ->
groupKDBX?.addChildEntry(entryToAdd)
}
group.groupKDBX?.getChildGroups()?.forEach { groupToAdd ->
groupKDBX?.addChildGroup(groupToAdd)
}
}
override fun touch(modified: Boolean, touchParents: Boolean) {
groupKDB?.touch(modified, touchParents)
groupKDBX?.touch(modified, touchParents)
}
override fun isContainedIn(container: Group): Boolean {
var contained: Boolean? = null
container.groupKDB?.let {
contained = groupKDB?.isContainedIn(it)
}
container.groupKDBX?.let {
contained = groupKDBX?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: DateInstant
get() = groupKDB?.creationTime ?: groupKDBX?.creationTime ?: DateInstant()
set(value) {
groupKDB?.creationTime = value
groupKDBX?.creationTime = value
}
override var lastModificationTime: DateInstant
get() = groupKDB?.lastModificationTime ?: groupKDBX?.lastModificationTime ?: DateInstant()
set(value) {
groupKDB?.lastModificationTime = value
groupKDBX?.lastModificationTime = value
}
override var lastAccessTime: DateInstant
get() = groupKDB?.lastAccessTime ?: groupKDBX?.lastAccessTime ?: DateInstant()
set(value) {
groupKDB?.lastAccessTime = value
groupKDBX?.lastAccessTime = value
}
override var expiryTime: DateInstant
get() = groupKDB?.expiryTime ?: groupKDBX?.expiryTime ?: DateInstant()
set(value) {
groupKDB?.expiryTime = value
groupKDBX?.expiryTime = value
}
override var expires: Boolean
get() = groupKDB?.expires ?: groupKDBX?.expires ?: false
set(value) {
groupKDB?.expires = value
groupKDBX?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = groupKDB?.isCurrentlyExpires ?: groupKDBX?.isCurrentlyExpires ?: false
override fun getChildGroups(): MutableList<Group> {
val children = ArrayList<Group>()
groupKDB?.getChildGroups()?.forEach {
children.add(Group(it))
}
groupKDBX?.getChildGroups()?.forEach {
children.add(Group(it))
}
return children
}
override fun getChildEntries(): MutableList<Entry> {
// To cal function with vararg
return getChildEntries(*emptyArray<ChildFilter>())
}
fun getChildEntries(vararg filter: ChildFilter): MutableList<Entry> {
val children = ArrayList<Entry>()
val withoutMetaStream = filter.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED)
groupKDB?.getChildEntries()?.forEach {
val entryToAddAsChild = Entry(it)
if ((!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream))
&& (!entryToAddAsChild.isCurrentlyExpires or showExpiredEntries))
children.add(entryToAddAsChild)
}
groupKDBX?.getChildEntries()?.forEach {
val entryToAddAsChild = Entry(it)
if (!entryToAddAsChild.isCurrentlyExpires or showExpiredEntries)
children.add(entryToAddAsChild)
}
return children
}
/**
* Filter entries and return children
* @return List of direct children (one level below) as NodeVersioned
*/
fun getChildren(vararg filter: ChildFilter): List<Node> {
val children = ArrayList<Node>()
children.addAll(getChildGroups())
groupKDB?.let {
children.addAll(getChildEntries(*filter))
}
groupKDBX?.let {
// No MetasStream in V4
children.addAll(getChildEntries(*filter))
}
return children
}
override fun addChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.addChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.addChildGroup(it)
}
}
override fun addChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.addChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.addChildEntry(it)
}
}
override fun removeChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.removeChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.removeChildGroup(it)
}
}
override fun removeChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.removeChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.removeChildEntry(it)
}
}
override fun removeChildren() {
groupKDB?.removeChildren()
groupKDBX?.removeChildren()
}
override fun allowAddEntryIfIsRoot(): Boolean {
return groupKDB?.allowAddEntryIfIsRoot() ?: groupKDBX?.allowAddEntryIfIsRoot() ?: false
}
/*
------------
KDB Methods
------------
*/
var nodeIdKDB: NodeId<Int>
get() = groupKDB?.nodeId ?: NodeIdInt()
set(value) { groupKDB?.nodeId = value }
fun setNodeId(id: NodeIdInt) {
groupKDB?.nodeId = id
}
fun getLevel(): Int {
return groupKDB?.level ?: -1
}
fun setLevel(level: Int) {
groupKDB?.level = level
}
/*
------------
KDBX Methods
------------
*/
var nodeIdKDBX: NodeId<UUID>
get() = groupKDBX?.nodeId ?: NodeIdUUID()
set(value) { groupKDBX?.nodeId = value }
fun setNodeId(id: NodeIdUUID) {
groupKDBX?.nodeId = id
}
fun setEnableAutoType(enableAutoType: Boolean?) {
groupKDBX?.enableAutoType = enableAutoType
}
fun setEnableSearching(enableSearching: Boolean?) {
groupKDBX?.enableSearching = enableSearching
}
fun setExpanded(expanded: Boolean) {
groupKDBX?.isExpanded = expanded
}
fun containsCustomData(): Boolean {
return groupKDBX?.containsCustomData() ?: false
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Group
if (groupKDB != other.groupKDB) return false
if (groupKDBX != other.groupKDBX) return false
return true
}
override fun hashCode(): Int {
var result = groupKDB?.hashCode() ?: 0
result = 31 * result + (groupKDBX?.hashCode() ?: 0)
return result
}
}

View File

@@ -1,363 +0,0 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import java.util.*
import kotlin.collections.ArrayList
class GroupVersioned : NodeVersioned, PwGroupInterface<GroupVersioned, EntryVersioned> {
var pwGroupV3: PwGroupV3? = null
private set
var pwGroupV4: PwGroupV4? = null
private set
fun updateWith(group: GroupVersioned) {
group.pwGroupV3?.let {
this.pwGroupV3?.updateWith(it)
}
group.pwGroupV4?.let {
this.pwGroupV4?.updateWith(it)
}
}
/**
* Use this constructor to copy a Group
*/
constructor(group: GroupVersioned) {
if (group.pwGroupV3 != null) {
if (this.pwGroupV3 == null)
this.pwGroupV3 = PwGroupV3()
}
if (group.pwGroupV4 != null) {
if (this.pwGroupV4 == null)
this.pwGroupV4 = PwGroupV4()
}
updateWith(group)
}
constructor(group: PwGroupV3) {
this.pwGroupV4 = null
this.pwGroupV3 = group
}
constructor(group: PwGroupV4) {
this.pwGroupV3 = null
this.pwGroupV4 = group
}
constructor(parcel: Parcel) {
pwGroupV3 = parcel.readParcelable(PwGroupV3::class.java.classLoader)
pwGroupV4 = parcel.readParcelable(PwGroupV4::class.java.classLoader)
}
companion object CREATOR : Parcelable.Creator<GroupVersioned> {
override fun createFromParcel(parcel: Parcel): GroupVersioned {
return GroupVersioned(parcel)
}
override fun newArray(size: Int): Array<GroupVersioned?> {
return arrayOfNulls(size)
}
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(pwGroupV3, flags)
dest.writeParcelable(pwGroupV4, flags)
}
override val nodeId: PwNodeId<*>?
get() = pwGroupV4?.nodeId ?: pwGroupV3?.nodeId
override var title: String
get() = pwGroupV3?.title ?: pwGroupV4?.title ?: ""
set(value) {
pwGroupV3?.title = value
pwGroupV4?.title = value
}
override var icon: PwIcon
get() = pwGroupV3?.icon ?: pwGroupV4?.icon ?: PwIconStandard()
set(value) {
pwGroupV3?.icon = value
pwGroupV4?.icon = value
}
override val type: Type
get() = Type.GROUP
override var parent: GroupVersioned?
get() {
pwGroupV3?.parent?.let {
return GroupVersioned(it)
}
pwGroupV4?.parent?.let {
return GroupVersioned(it)
}
return null
}
set(value) {
pwGroupV3?.parent = value?.pwGroupV3
pwGroupV4?.parent = value?.pwGroupV4
}
override fun containsParent(): Boolean {
return pwGroupV3?.containsParent() ?: pwGroupV4?.containsParent() ?: false
}
override fun afterAssignNewParent() {
pwGroupV3?.afterAssignNewParent()
pwGroupV4?.afterAssignNewParent()
}
fun addChildrenFrom(group: GroupVersioned) {
group.pwGroupV3?.getChildEntries()?.forEach { entryToAdd ->
pwGroupV3?.addChildEntry(entryToAdd)
}
group.pwGroupV3?.getChildGroups()?.forEach { groupToAdd ->
pwGroupV3?.addChildGroup(groupToAdd)
}
group.pwGroupV4?.getChildEntries()?.forEach { entryToAdd ->
pwGroupV4?.addChildEntry(entryToAdd)
}
group.pwGroupV4?.getChildGroups()?.forEach { groupToAdd ->
pwGroupV4?.addChildGroup(groupToAdd)
}
}
fun removeChildren() {
pwGroupV3?.getChildEntries()?.forEach { entryToRemove ->
pwGroupV3?.removeChildEntry(entryToRemove)
}
pwGroupV3?.getChildGroups()?.forEach { groupToRemove ->
pwGroupV3?.removeChildGroup(groupToRemove)
}
pwGroupV4?.getChildEntries()?.forEach { entryToRemove ->
pwGroupV4?.removeChildEntry(entryToRemove)
}
pwGroupV4?.getChildGroups()?.forEach { groupToRemove ->
pwGroupV4?.removeChildGroup(groupToRemove)
}
}
override fun touch(modified: Boolean, touchParents: Boolean) {
pwGroupV3?.touch(modified, touchParents)
pwGroupV4?.touch(modified, touchParents)
}
override fun isContainedIn(container: GroupVersioned): Boolean {
var contained: Boolean? = null
container.pwGroupV3?.let {
contained = pwGroupV3?.isContainedIn(it)
}
container.pwGroupV4?.let {
contained = pwGroupV4?.isContainedIn(it)
}
return contained ?: false
}
override var creationTime: PwDate
get() = pwGroupV3?.creationTime ?: pwGroupV4?.creationTime ?: PwDate()
set(value) {
pwGroupV3?.creationTime = value
pwGroupV4?.creationTime = value
}
override var lastModificationTime: PwDate
get() = pwGroupV3?.lastModificationTime ?: pwGroupV4?.lastModificationTime ?: PwDate()
set(value) {
pwGroupV3?.lastModificationTime = value
pwGroupV4?.lastModificationTime = value
}
override var lastAccessTime: PwDate
get() = pwGroupV3?.lastAccessTime ?: pwGroupV4?.lastAccessTime ?: PwDate()
set(value) {
pwGroupV3?.lastAccessTime = value
pwGroupV4?.lastAccessTime = value
}
override var expiryTime: PwDate
get() = pwGroupV3?.expiryTime ?: pwGroupV4?.expiryTime ?: PwDate()
set(value) {
pwGroupV3?.expiryTime = value
pwGroupV4?.expiryTime = value
}
override var expires: Boolean
get() = pwGroupV3?.expires ?: pwGroupV4?.expires ?: false
set(value) {
pwGroupV3?.expires = value
pwGroupV4?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = pwGroupV3?.isCurrentlyExpires ?: pwGroupV4?.isCurrentlyExpires ?: false
override fun getChildGroups(): MutableList<GroupVersioned> {
val children = ArrayList<GroupVersioned>()
pwGroupV3?.getChildGroups()?.forEach {
children.add(GroupVersioned(it))
}
pwGroupV4?.getChildGroups()?.forEach {
children.add(GroupVersioned(it))
}
return children
}
override fun getChildEntries(): MutableList<EntryVersioned> {
return getChildEntries(false)
}
fun getChildEntries(withoutMetaStream: Boolean): MutableList<EntryVersioned> {
val children = ArrayList<EntryVersioned>()
pwGroupV3?.getChildEntries()?.forEach {
val entryToAddAsChild = EntryVersioned(it)
if (!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream))
children.add(entryToAddAsChild)
}
pwGroupV4?.getChildEntries()?.forEach {
children.add(EntryVersioned(it))
}
return children
}
/**
* Filter MetaStream entries and return children
* @return List of direct children (one level below) as PwNode
*/
fun getChildren(withoutMetaStream: Boolean = true): List<NodeVersioned> {
val children = ArrayList<NodeVersioned>()
children.addAll(getChildGroups())
pwGroupV3?.let {
children.addAll(getChildEntries(withoutMetaStream))
}
pwGroupV4?.let {
// No MetasStream in V4
children.addAll(getChildEntries(withoutMetaStream))
}
return children
}
override fun addChildGroup(group: GroupVersioned) {
group.pwGroupV3?.let {
pwGroupV3?.addChildGroup(it)
}
group.pwGroupV4?.let {
pwGroupV4?.addChildGroup(it)
}
}
override fun addChildEntry(entry: EntryVersioned) {
entry.pwEntryV3?.let {
pwGroupV3?.addChildEntry(it)
}
entry.pwEntryV4?.let {
pwGroupV4?.addChildEntry(it)
}
}
override fun removeChildGroup(group: GroupVersioned) {
group.pwGroupV3?.let {
pwGroupV3?.removeChildGroup(it)
}
group.pwGroupV4?.let {
pwGroupV4?.removeChildGroup(it)
}
}
override fun removeChildEntry(entry: EntryVersioned) {
entry.pwEntryV3?.let {
pwGroupV3?.removeChildEntry(it)
}
entry.pwEntryV4?.let {
pwGroupV4?.removeChildEntry(it)
}
}
override fun allowAddEntryIfIsRoot(): Boolean {
return pwGroupV3?.allowAddEntryIfIsRoot() ?: pwGroupV4?.allowAddEntryIfIsRoot() ?: false
}
/*
------------
V3 Methods
------------
*/
var nodeIdV3: PwNodeId<Int>
get() = pwGroupV3?.nodeId ?: PwNodeIdInt()
set(value) { pwGroupV3?.nodeId = value }
fun setNodeId(id: PwNodeIdInt) {
pwGroupV3?.nodeId = id
}
fun getLevel(): Int {
return pwGroupV3?.level ?: -1
}
fun setLevel(level: Int) {
pwGroupV3?.level = level
}
/*
------------
V4 Methods
------------
*/
var nodeIdV4: PwNodeId<UUID>
get() = pwGroupV4?.nodeId ?: PwNodeIdUUID()
set(value) { pwGroupV4?.nodeId = value }
fun setNodeId(id: PwNodeIdUUID) {
pwGroupV4?.nodeId = id
}
fun setEnableAutoType(enableAutoType: Boolean?) {
pwGroupV4?.enableAutoType = enableAutoType
}
fun setEnableSearching(enableSearching: Boolean?) {
pwGroupV4?.enableSearching = enableSearching
}
fun setExpanded(expanded: Boolean) {
pwGroupV4?.isExpanded = expanded
}
fun containsCustomData(): Boolean {
return pwGroupV4?.containsCustomData() ?: false
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GroupVersioned
if (pwGroupV3 != other.pwGroupV3) return false
if (pwGroupV4 != other.pwGroupV4) return false
return true
}
override fun hashCode(): Int {
var result = pwGroupV3?.hashCode() ?: 0
result = 31 * result + (pwGroupV4?.hashCode() ?: 0)
return result
}
}

View File

@@ -1,34 +0,0 @@
package com.kunzisoft.keepass.database.element
interface NodeVersioned: PwNodeInterface<GroupVersioned> {
val nodeId: PwNodeId<*>?
val nodePositionInParent: Int
get() {
parent?.getChildren(true)?.let { children ->
children.forEachIndexed { index, nodeVersioned ->
if (nodeVersioned.nodeId == this.nodeId)
return index
}
}
return -1
}
fun addParentFrom(node: NodeVersioned) {
parent = node.parent
}
fun removeParent() {
parent = null
}
}
/**
* Type of available Nodes
*/
enum class Type {
GROUP, ENTRY
}

View File

@@ -1,275 +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.database.element
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ConfigurationCompat
import com.kunzisoft.keepass.utils.Types
import java.util.*
/**
* Converting from the C Date format to the Java data format is
* expensive when done for every record at once.
*/
class PwDate : Parcelable {
private var jDate: Date = Date()
private var jDateBuilt = false
@Transient
private var cDate: ByteArray? = null
@Transient
private var cDateBuilt = false
val date: Date
get() {
if (!jDateBuilt) {
jDate = readTime(cDate, 0, calendar)
jDateBuilt = true
}
return jDate
}
val byteArrayDate: ByteArray?
get() {
if (!cDateBuilt) {
cDate = writeTime(jDate, calendar)
cDateBuilt = true
}
return cDate
}
constructor(buf: ByteArray, offset: Int) {
cDate = ByteArray(DATE_SIZE)
System.arraycopy(buf, offset, cDate!!, 0, DATE_SIZE)
cDateBuilt = true
}
constructor(source: PwDate) {
this.jDate = Date(source.jDate.time)
this.jDateBuilt = source.jDateBuilt
if (source.cDate != null) {
val dateLength = source.cDate!!.size
this.cDate = ByteArray(dateLength)
System.arraycopy(source.cDate!!, 0, this.cDate!!, 0, dateLength)
}
this.cDateBuilt = source.cDateBuilt
}
constructor(date: Date) {
jDate = Date(date.time)
jDateBuilt = true
}
constructor(millis: Long) {
jDate = Date(millis)
jDateBuilt = true
}
constructor() {
jDate = Date()
jDateBuilt = true
}
protected constructor(parcel: Parcel) {
jDate = parcel.readSerializable() as Date
jDateBuilt = parcel.readByte().toInt() != 0
cDateBuilt = false
}
override fun describeContents(): Int {
return 0
}
fun getDateTimeString(resources: Resources): String {
return Companion.getDateTimeString(resources, this.date)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(date)
dest.writeByte((if (jDateBuilt) 1 else 0).toByte())
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null) {
return false
}
if (javaClass != other.javaClass) {
return false
}
val date = other as PwDate?
return if (cDateBuilt && date!!.cDateBuilt) {
Arrays.equals(cDate, date.cDate)
} else if (jDateBuilt && date!!.jDateBuilt) {
isSameDate(jDate, date.jDate)
} else if (cDateBuilt && date!!.jDateBuilt) {
Arrays.equals(date.byteArrayDate, cDate)
} else {
isSameDate(date!!.date, jDate)
}
}
override fun hashCode(): Int {
var result = jDate.hashCode()
result = 31 * result + jDateBuilt.hashCode()
result = 31 * result + (cDate?.contentHashCode() ?: 0)
result = 31 * result + cDateBuilt.hashCode()
return result
}
companion object {
private const val DATE_SIZE = 5
private var mCalendar: Calendar? = null
val NEVER_EXPIRE = neverExpire
private val calendar: Calendar?
get() {
if (mCalendar == null) {
mCalendar = Calendar.getInstance()
}
return mCalendar
}
private val neverExpire: PwDate
get() {
val cal = Calendar.getInstance()
cal.set(Calendar.YEAR, 2999)
cal.set(Calendar.MONTH, 11)
cal.set(Calendar.DAY_OF_MONTH, 28)
cal.set(Calendar.HOUR, 23)
cal.set(Calendar.MINUTE, 59)
cal.set(Calendar.SECOND, 59)
return PwDate(cal.time)
}
@JvmField
val CREATOR: Parcelable.Creator<PwDate> = object : Parcelable.Creator<PwDate> {
override fun createFromParcel(parcel: Parcel): PwDate {
return PwDate(parcel)
}
override fun newArray(size: Int): Array<PwDate?> {
return arrayOfNulls(size)
}
}
/**
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
* to a java.util.Date instance.
*/
fun readTime(buf: ByteArray?, offset: Int, calendar: Calendar?): Date {
var time = calendar
val dw1 = Types.readUByte(buf!!, offset)
val dw2 = Types.readUByte(buf, offset + 1)
val dw3 = Types.readUByte(buf, offset + 2)
val dw4 = Types.readUByte(buf, offset + 3)
val dw5 = Types.readUByte(buf, offset + 4)
// Unpack 5 byte structure to date and time
val year = dw1 shl 6 or (dw2 shr 2)
val month = dw2 and 0x00000003 shl 2 or (dw3 shr 6)
val day = dw3 shr 1 and 0x0000001F
val hour = dw3 and 0x00000001 shl 4 or (dw4 shr 4)
val minute = dw4 and 0x0000000F shl 2 or (dw5 shr 6)
val second = dw5 and 0x0000003F
if (time == null) {
time = Calendar.getInstance()
}
// File format is a 1 based month, java Calendar uses a zero based month
// File format is a 1 based day, java Calendar uses a 1 based day
time!!.set(year, month - 1, day, hour, minute, second)
return time.time
}
@JvmOverloads
fun writeTime(date: Date?, calendar: Calendar? = null): ByteArray? {
var cal = calendar
if (date == null) {
return null
}
val buf = ByteArray(5)
if (cal == null) {
cal = Calendar.getInstance()
}
cal!!.time = date
val year = cal.get(Calendar.YEAR)
// File format is a 1 based month, java Calendar uses a zero based month
val month = cal.get(Calendar.MONTH) + 1
// File format is a 0 based day, java Calendar uses a 1 based day
val day = cal.get(Calendar.DAY_OF_MONTH) - 1
val hour = cal.get(Calendar.HOUR_OF_DAY)
val minute = cal.get(Calendar.MINUTE)
val second = cal.get(Calendar.SECOND)
buf[0] = Types.writeUByte(year shr 6 and 0x0000003F)
buf[1] = Types.writeUByte(year and 0x0000003F shl 2 or (month shr 2 and 0x00000003))
buf[2] = (month and 0x00000003 shl 6
or (day and 0x0000001F shl 1) or (hour shr 4 and 0x00000001)).toByte()
buf[3] = (hour and 0x0000000F shl 4 or (minute shr 2 and 0x0000000F)).toByte()
buf[4] = (minute and 0x00000003 shl 6 or (second and 0x0000003F)).toByte()
return buf
}
private fun isSameDate(d1: Date?, d2: Date?): Boolean {
val cal1 = Calendar.getInstance()
cal1.time = d1
cal1.set(Calendar.MILLISECOND, 0)
val cal2 = Calendar.getInstance()
cal2.time = d2
cal2.set(Calendar.MILLISECOND, 0)
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) &&
cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH) &&
cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) &&
cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) &&
cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND)
}
fun getDateTimeString(resources: Resources, date: Date): String {
return java.text.DateFormat.getDateTimeInstance(
java.text.DateFormat.MEDIUM,
java.text.DateFormat.MEDIUM,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(date)
}
}
}

View File

@@ -1,19 +0,0 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import java.util.*
abstract class PwEntry
<
GroupId,
EntryId,
ParentGroup: PwGroup<GroupId, EntryId, ParentGroup, Entry>,
Entry: PwEntry<GroupId, EntryId, ParentGroup, Entry>
>
: PwNode<EntryId, ParentGroup, Entry>, PwEntryInterface<ParentGroup> {
constructor() : super()
constructor(parcel: Parcel) : super(parcel)
}

View File

@@ -1,12 +0,0 @@
package com.kunzisoft.keepass.database.element
interface PwEntryInterface<ParentGroup> : PwNodeInterface<ParentGroup> {
var username: String
var password: String
var url: String
var notes: String
}

View File

@@ -1,204 +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.database.element
import android.os.Parcel
import android.os.Parcelable
import java.io.UnsupportedEncodingException
import java.util.Arrays
import java.util.UUID
/**
* Structure containing information about one entry.
*
* <PRE>
* One entry: [FIELDTYPE(FT)][FIELDSIZE(FS)][FIELDDATA(FD)]
* [FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)]...
*
* [ 2 bytes] FIELDTYPE
* [ 4 bytes] FIELDSIZE, size of FIELDDATA in bytes
* [ n bytes] FIELDDATA, n = FIELDSIZE
*
* Notes:
* - Strings are stored in UTF-8 encoded form and are null-terminated.
* - FIELDTYPE can be one of the FT_ constants.
</PRE> *
*
* @author Naomaru Itoi <nao></nao>@phoneid.org>
* @author Bill Zwicky <wrzwicky></wrzwicky>@pobox.com>
* @author Dominik Reichl <dominik.reichl></dominik.reichl>@t-online.de>
* @author Jeremy Jamet <jeremy.jamet></jeremy.jamet>@kunzisoft.com>
*/
class PwEntryV3 : PwEntry<Int, UUID, PwGroupV3, PwEntryV3>, PwNodeV3Interface {
/** A string describing what is in pBinaryData */
var binaryDesc = ""
/**
* @return the actual binaryData byte array.
*/
var binaryData: ByteArray = ByteArray(0)
private set
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
get() {
if (Arrays.equals(binaryData, ByteArray(0))) return false
if (notes.isEmpty()) return false
if (binaryDesc != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
if (title != PMS_ID_TITLE) return false
if (username.isEmpty()) return false
if (username != PMS_ID_USER) return false
if (url.isEmpty()) return false
return if (url != PMS_ID_URL) false else icon.isMetaStreamIcon
}
override fun initNodeId(): PwNodeId<UUID> {
return PwNodeIdUUID()
}
override fun copyNodeId(nodeId: PwNodeId<UUID>): PwNodeId<UUID> {
return PwNodeIdUUID(nodeId.id)
}
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
title = parcel.readString() ?: title
username = parcel.readString() ?: username
parcel.readByteArray(passwordBytes)
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
binaryDesc = parcel.readString() ?: binaryDesc
parcel.readByteArray(binaryData)
}
override fun readParentParcelable(parcel: Parcel): PwGroupV3? {
return parcel.readParcelable(PwGroupV3::class.java.classLoader)
}
override fun writeParentParcelable(parent: PwGroupV3?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(title)
dest.writeString(username)
dest.writeByteArray(passwordBytes)
dest.writeString(url)
dest.writeString(notes)
dest.writeString(binaryDesc)
dest.writeByteArray(binaryData)
}
fun updateWith(source: PwEntryV3) {
super.updateWith(source)
title = source.title
username = source.username
val passLen = source.passwordBytes.size
passwordBytes = ByteArray(passLen)
System.arraycopy(source.passwordBytes, 0, passwordBytes, 0, passLen)
url = source.url
notes = source.notes
binaryDesc = source.binaryDesc
val descLen = source.binaryData.size
binaryData = ByteArray(descLen)
System.arraycopy(source.binaryData, 0, binaryData, 0, descLen)
}
override var username = ""
var passwordBytes: ByteArray = ByteArray(0)
private set
/** Securely erase old password before copying new. */
fun setPassword(buf: ByteArray, offset: Int, len: Int) {
fill(passwordBytes, 0.toByte())
passwordBytes = ByteArray(len)
System.arraycopy(buf, offset, passwordBytes, 0, len)
}
/**
* @return the actual password byte array.
*/
override var password: String
get() = String(passwordBytes)
set(pass) {
var password: ByteArray
try {
password = pass.toByteArray(charset("UTF-8"))
setPassword(password, 0, password.size)
} catch (e: UnsupportedEncodingException) {
password = pass.toByteArray()
setPassword(password, 0, password.size)
}
}
override var url = ""
override var notes = ""
override var title = ""
override val type: Type
get() = Type.ENTRY
fun setBinaryData(buf: ByteArray, offset: Int, len: Int) {
/** Securely erase old data before copying new. */
fill(binaryData, 0.toByte())
binaryData = ByteArray(len)
System.arraycopy(buf, offset, binaryData, 0, len)
}
companion object {
/** Size of byte buffer needed to hold this struct. */
private const val PMS_ID_BINDESC = "bin-stream"
private const val PMS_ID_TITLE = "Meta-Info"
private const val PMS_ID_USER = "SYSTEM"
private const val PMS_ID_URL = "$"
@JvmField
val CREATOR: Parcelable.Creator<PwEntryV3> = object : Parcelable.Creator<PwEntryV3> {
override fun createFromParcel(`in`: Parcel): PwEntryV3 {
return PwEntryV3(`in`)
}
override fun newArray(size: Int): Array<PwEntryV3?> {
return arrayOfNulls(size)
}
}
/**
* fill byte array
*/
private fun fill(array: ByteArray, value: Byte) {
for (i in array.indices)
array[i] = value
}
}
}

View File

@@ -1,31 +0,0 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcelable
interface PwNodeInterface<ParentGroup> : NodeTimeInterface, Parcelable {
var title: String
/**
* @return Visual icon
*/
var icon: PwIcon
/**
* @return Type of Node
*/
val type: Type
/**
* Retrieve the parent node
*/
var parent: ParentGroup?
fun containsParent(): Boolean
fun afterAssignNewParent()
fun isContainedIn(container: ParentGroup): Boolean
fun touch(modified: Boolean, touchParents: Boolean)
}

View File

@@ -1,18 +0,0 @@
package com.kunzisoft.keepass.database.element
import org.joda.time.LocalDateTime
interface PwNodeV3Interface : NodeTimeInterface {
override var expires: Boolean
// If expireDate is before NEVER_EXPIRE date less 1 month (to be sure)
// it is not expires
get() = LocalDateTime(expiryTime.date)
.isBefore(LocalDateTime.fromDateFields(PwDate.NEVER_EXPIRE.date)
.minusMonths(1))
set(value) {
if (!value)
expiryTime = PwDate.NEVER_EXPIRE
}
}

View File

@@ -18,18 +18,16 @@
*
*/
package com.kunzisoft.keepass.database
package com.kunzisoft.keepass.database.element
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 com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.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> {
fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator<Node> {
return when (this) {
DB -> NodeNaturalComparator(ascending, groupsBefore, false) // Force false because natural order contains recycle bin
TITLE -> NodeTitleComparator(ascending, groupsBefore, recycleBinBottom)
@@ -40,11 +38,11 @@ enum class SortNodeEnum {
}
}
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<NodeVersioned> {
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<Node> {
abstract fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int
abstract fun compareBySpecificOrder(object1: Node, object2: Node): Int
private fun specificOrderOrHashIfEquals(object1: NodeVersioned, object2: NodeVersioned): Int {
private fun specificOrderOrHashIfEquals(object1: Node, object2: Node): Int {
val specificOrderComp = compareBySpecificOrder(object1, object2)
return if (specificOrderComp == 0) {
@@ -52,20 +50,20 @@ enum class SortNodeEnum {
} else if (!ascending) -specificOrderComp else specificOrderComp // If descending, revert
}
override fun compare(object1: NodeVersioned,object2: NodeVersioned): Int {
override fun compare(object1: Node, object2: Node): 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)
val database = Database.getInstance()
if (database.isRecycleBinEnabled && recycleBinBottom) {
if (database.recycleBin == object1)
return 1
if (Database.getInstance().recycleBin == object2)
if (database.recycleBin == object2)
return -1
}
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.ENTRY) {
if (groupsBefore)
@@ -99,7 +97,7 @@ enum class SortNodeEnum {
class NodeNaturalComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.nodePositionInParent.compareTo(object2.nodePositionInParent)
}
}
@@ -110,7 +108,7 @@ enum class SortNodeEnum {
class NodeTitleComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.title.compareTo(object2.title, ignoreCase = true)
}
}
@@ -121,11 +119,11 @@ enum class SortNodeEnum {
class NodeUsernameComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
override fun compareBySpecificOrder(object1: Node, object2: Node): 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,
return (object1 as Entry).getEntryInfo(Database.getInstance()).username
.compareTo((object2 as Entry).getEntryInfo(Database.getInstance()).username,
ignoreCase = true)
}
return NodeTitleComparator(ascending, groupsBefore, recycleBinBottom).compare(object1, object2)
@@ -138,7 +136,7 @@ enum class SortNodeEnum {
class NodeCreationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.creationTime.date
.compareTo(object2.creationTime.date)
}
@@ -150,7 +148,7 @@ enum class SortNodeEnum {
class NodeLastModificationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.lastModificationTime.date
.compareTo(object2.lastModificationTime.date)
}
@@ -162,7 +160,7 @@ enum class SortNodeEnum {
class NodeLastAccessComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.lastAccessTime.date
.compareTo(object2.lastAccessTime.date)
}

View File

@@ -17,28 +17,30 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.database
import android.util.SparseArray
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import java.io.IOException
class BinaryPool {
private val pool = SparseArray<ProtectedBinary>()
private val pool = SparseArray<BinaryAttachment>()
operator fun get(key: Int): ProtectedBinary? {
operator fun get(key: Int): BinaryAttachment? {
return pool[key]
}
fun put(key: Int, value: ProtectedBinary) {
fun put(key: Int, value: BinaryAttachment) {
pool.put(key, value)
}
fun doForEachBinary(action: (key: Int, binary: ProtectedBinary) -> Unit) {
fun doForEachBinary(action: (key: Int, binary: BinaryAttachment) -> Unit) {
for (i in 0 until pool.size()) {
action.invoke(i, pool.get(pool.keyAt(i)))
}
}
@Throws(IOException::class)
fun clear() {
doForEachBinary { _, binary ->
binary.clear()
@@ -46,9 +48,10 @@ class BinaryPool {
pool.clear()
}
fun add(protectedBinary: ProtectedBinary) {
if (findKey(protectedBinary) != -1) return
pool.put(findUnusedKey(), protectedBinary)
fun add(fileBinary: BinaryAttachment) {
if (findKey(fileBinary) == null) {
pool.put(findUnusedKey(), fileBinary)
}
}
fun findUnusedKey(): Int {
@@ -58,10 +61,10 @@ class BinaryPool {
return unusedKey
}
fun findKey(pb: ProtectedBinary): Int {
fun findKey(pb: BinaryAttachment): Int? {
for (i in 0 until pool.size()) {
if (pool.get(pool.keyAt(i)) == pb) return i
}
return -1
return null
}
}

View File

@@ -17,20 +17,33 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.database
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
// Note: We can get away with using int's to store unsigned 32-bit ints
// since we won't do arithmetic on these values (also unlikely to
// reach negative ids).
enum class PwCompressionAlgorithm : ObjectNameResource {
enum class CompressionAlgorithm : ObjectNameResource, Parcelable {
None,
GZip;
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeEnum(this)
}
override fun describeContents(): Int {
return 0
}
override fun getName(resources: Resources): String {
return when (this) {
None -> resources.getString(R.string.compression_none)
@@ -38,4 +51,14 @@ enum class PwCompressionAlgorithm : ObjectNameResource {
}
}
companion object CREATOR : Parcelable.Creator<CompressionAlgorithm> {
override fun createFromParcel(parcel: Parcel): CompressionAlgorithm {
return parcel.readEnum<CompressionAlgorithm>() ?: None
}
override fun newArray(size: Int): Array<CompressionAlgorithm?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -17,11 +17,17 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.IOException
import java.io.InputStream
@@ -31,10 +37,12 @@ import java.security.NoSuchAlgorithmException
import java.util.*
import kotlin.collections.ArrayList
class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var numKeyEncRounds: Int = 0
var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
override val version: String
@@ -44,22 +52,32 @@ class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
kdfListV3.add(KdfFactory.aesKdf)
}
private fun getGroupById(groupId: Int): GroupKDB? {
if (groupId == -1)
return null
return getGroupById(NodeIdInt(groupId))
}
// Retrieve backup group in index
val backupGroup: GroupKDB?
get() = if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID) null else getGroupById(backupGroupId)
override val kdfEngine: KdfEngine?
get() = kdfListV3[0]
override val kdfAvailableList: List<KdfEngine>
get() = kdfListV3
override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
get() {
val list = ArrayList<PwEncryptionAlgorithm>()
list.add(PwEncryptionAlgorithm.AESRijndael)
val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael)
return list
}
val rootGroups: List<PwGroupV3>
val rootGroups: List<GroupKDB>
get() {
val kids = ArrayList<PwGroupV3>()
val kids = ArrayList<GroupKDB>()
doForEachGroupInIndex { group ->
if (group.level == 0)
kids.add(group)
@@ -81,7 +99,7 @@ class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
}
init {
algorithm = PwEncryptionAlgorithm.AESRijndael
algorithm = EncryptionAlgorithm.AESRijndael
numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS
}
@@ -90,10 +108,10 @@ class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
*
* @return new tree id
*/
override fun newGroupId(): PwNodeIdInt {
var newId: PwNodeIdInt
override fun newGroupId(): NodeIdInt {
var newId: NodeIdInt
do {
newId = PwNodeIdInt()
newId = NodeIdInt()
} while (isGroupIdUsed(newId))
return newId
@@ -104,10 +122,10 @@ class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
*
* @return new tree id
*/
override fun newEntryId(): PwNodeIdUUID {
var newId: PwNodeIdUUID
override fun newEntryId(): NodeIdUUID {
var newId: NodeIdUUID
do {
newId = PwNodeIdUUID()
newId = NodeIdUUID()
} while (isEntryIdUsed(newId))
return newId
@@ -152,12 +170,12 @@ class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
return null
}
override fun createGroup(): PwGroupV3 {
return PwGroupV3()
override fun createGroup(): GroupKDB {
return GroupKDB()
}
override fun createEntry(): PwEntryV3 {
return PwEntryV3()
override fun createEntry(): EntryKDB {
return EntryKDB()
}
override fun rootCanContainsEntry(): Boolean {
@@ -168,10 +186,16 @@ class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
return false
}
override fun isBackup(group: PwGroupV3): Boolean {
var currentGroup: PwGroupV3? = group
override fun isInRecycleBin(group: GroupKDB): Boolean {
var currentGroup: GroupKDB? = group
if (currentGroup == backupGroup)
return true
while (currentGroup != null) {
if (currentGroup.level == 0 && currentGroup.title.equals("Backup", ignoreCase = true)) {
if (currentGroup.level == 0
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
backupGroupId = currentGroup.id
return true
}
currentGroup = currentGroup.parent
@@ -179,10 +203,72 @@ class PwDatabaseV3 : PwDatabase<Int, UUID, PwGroupV3, PwEntryV3>() {
return false
}
/**
* Ensure that the recycle bin tree exists, if enabled and create it
* if it doesn't exist
*/
fun ensureRecycleBinExists() {
rootGroups.forEach { currentGroup ->
if (currentGroup.level == 0
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
backupGroupId = currentGroup.id
}
}
if (backupGroup == null) {
// Create recycle bin
val recycleBinGroup = createGroup().apply {
title = BACKUP_FOLDER_TITLE
icon = iconFactory.trashIcon
}
addGroupTo(recycleBinGroup, rootGroup)
backupGroupId = recycleBinGroup.id
}
}
/**
* Define if a Node must be delete or recycle when remove action is called
* @param node Node to remove
* @return true if node can be recycle, false elsewhere
*/
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
// TODO #394 Backup pw3
return true
}
fun recycle(group: GroupKDB) {
ensureRecycleBinExists()
removeGroupFrom(group, group.parent)
addGroupTo(group, backupGroup)
group.afterAssignNewParent()
}
fun recycle(entry: EntryKDB) {
ensureRecycleBinExists()
removeEntryFrom(entry, entry.parent)
addEntryTo(entry, backupGroup)
entry.afterAssignNewParent()
}
fun undoRecycle(group: GroupKDB, origParent: GroupKDB) {
removeGroupFrom(group, backupGroup)
addGroupTo(group, origParent)
}
fun undoRecycle(entry: EntryKDB, origParent: GroupKDB) {
removeEntryFrom(entry, backupGroup)
addEntryTo(entry, origParent)
}
companion object {
const val BACKUP_FOLDER_TITLE = "Backup"
private const val BACKUP_FOLDER_UNDEFINED_ID = -1
private const val DEFAULT_ENCRYPTION_ROUNDS = 300
const val BUFFER_SIZE_BYTES = 3 * 128
/**
* Encrypt the master key a few times to make brute-force key-search harder
* @throws IOException

View File

@@ -17,17 +17,30 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.database
import android.content.res.Resources
import android.util.Base64
import android.util.Log
import biz.source_code.base64Coder.Base64Coder
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.*
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node
import org.w3c.dom.Text
@@ -41,29 +54,30 @@ import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var hmacKey: ByteArray? = null
private set
var dataCipher = AesEngine.CIPHER_UUID
private var dataEngine: CipherEngine = AesEngine()
var compressionAlgorithm = PwCompressionAlgorithm.GZip
var compressionAlgorithm = CompressionAlgorithm.GZip
var kdfParameters: KdfParameters? = null
private var kdfV4List: MutableList<KdfEngine> = ArrayList()
private var kdfList: MutableList<KdfEngine> = ArrayList()
private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary()
var name = "KeePass DX database"
var nameChanged = PwDate()
var kdbxVersion: Long = 0
var name = ""
var nameChanged = DateInstant()
// TODO change setting date
var settingsChanged = PwDate()
var settingsChanged = DateInstant()
var description = ""
var descriptionChanged = PwDate()
var descriptionChanged = DateInstant()
var defaultUserName = ""
var defaultUserNameChanged = PwDate()
var defaultUserNameChanged = DateInstant()
// TODO date
var keyLastChanged = PwDate()
var keyLastChanged = DateInstant()
var keyChangeRecDays: Long = -1
var keyChangeForceDays: Long = 1
var isKeyChangeForceOnce = false
@@ -78,57 +92,68 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
var recycleBinUUID: UUID = UUID_ZERO
var recycleBinChanged = Date()
var entryTemplatesGroup = UUID_ZERO
var entryTemplatesGroupChanged = PwDate()
var entryTemplatesGroupChanged = DateInstant()
var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS
var historyMaxSize = DEFAULT_HISTORY_MAX_SIZE
var lastSelectedGroupUUID = UUID_ZERO
var lastTopVisibleGroupUUID = UUID_ZERO
var memoryProtection = MemoryProtectionConfig()
val deletedObjects = ArrayList<PwDeletedObject>()
val customIcons = ArrayList<PwIconCustom>()
val deletedObjects = ArrayList<DeletedObject>()
val customIcons = ArrayList<IconImageCustom>()
val customData = HashMap<String, String>()
var binPool = BinaryPool()
var binaryPool = BinaryPool()
var localizedAppName = "KeePassDX" // TODO resource
var localizedAppName = "KeePassDX"
init {
kdfV4List.add(KdfFactory.aesKdf)
kdfV4List.add(KdfFactory.argon2Kdf)
kdfList.add(KdfFactory.aesKdf)
kdfList.add(KdfFactory.argon2Kdf)
}
constructor()
constructor(databaseName: String) {
val groupV4 = createGroup().apply {
title = databaseName
/**
* Create a new database with a root group
*/
constructor(databaseName: String, rootName: String) {
name = databaseName
val group = createGroup().apply {
title = rootName
icon = iconFactory.folderIcon
}
rootGroup = groupV4
addGroupIndex(groupV4)
rootGroup = group
addGroupIndex(group)
}
override val version: String
get() = "KeePass 2"
get() {
val kdbxStringVersion = when(kdbxVersion) {
FILE_VERSION_32_3 -> "3.1"
FILE_VERSION_32_4 -> "4.0"
else -> "UNKNOWN"
}
return "KeePass 2 - KDBX$kdbxStringVersion"
}
override val kdfEngine: KdfEngine?
get() = try {
getEngineV4(kdfParameters)
getEngineKDBX4(kdfParameters)
} catch (unknownKDF: UnknownKDF) {
Log.i(TAG, "Unable to retrieve KDF engine", unknownKDF)
null
}
override val kdfAvailableList: List<KdfEngine>
get() = kdfV4List
get() = kdfList
@Throws(UnknownKDF::class)
fun getEngineV4(kdfParameters: KdfParameters?): KdfEngine {
fun getEngineKDBX4(kdfParameters: KdfParameters?): KdfEngine {
val unknownKDFException = UnknownKDF()
if (kdfParameters == null) {
throw unknownKDFException
}
for (engine in kdfV4List) {
for (engine in kdfList) {
if (engine.uuid == kdfParameters.uuid) {
return engine
}
@@ -136,20 +161,53 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
throw unknownKDFException
}
val availableCompressionAlgorithms: List<PwCompressionAlgorithm>
val availableCompressionAlgorithms: List<CompressionAlgorithm>
get() {
val list = ArrayList<PwCompressionAlgorithm>()
list.add(PwCompressionAlgorithm.None)
list.add(PwCompressionAlgorithm.GZip)
val list = ArrayList<CompressionAlgorithm>()
list.add(CompressionAlgorithm.None)
list.add(CompressionAlgorithm.GZip)
return list
}
override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
fun changeBinaryCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) {
binaryPool.doForEachBinary { key, binary ->
try {
when (oldCompression) {
CompressionAlgorithm.None -> {
when (newCompression) {
CompressionAlgorithm.None -> {
}
CompressionAlgorithm.GZip -> {
// To compress, create a new binary with file
binary.compress(BUFFER_SIZE_BYTES)
}
}
}
CompressionAlgorithm.GZip -> {
when (newCompression) {
CompressionAlgorithm.None -> {
// To decompress, create a new binary with file
binary.decompress(BUFFER_SIZE_BYTES)
}
CompressionAlgorithm.GZip -> {
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to change compression for $key")
}
}
}
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
get() {
val list = ArrayList<PwEncryptionAlgorithm>()
list.add(PwEncryptionAlgorithm.AESRijndael)
list.add(PwEncryptionAlgorithm.Twofish)
list.add(PwEncryptionAlgorithm.ChaCha20)
val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael)
list.add(EncryptionAlgorithm.Twofish)
list.add(EncryptionAlgorithm.ChaCha20)
return list
}
@@ -197,31 +255,31 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
override val passwordEncoding: String
get() = "UTF-8"
fun getGroupByUUID(groupUUID: UUID): PwGroupV4? {
private fun getGroupByUUID(groupUUID: UUID): GroupKDBX? {
if (groupUUID == UUID_ZERO)
return null
return getGroupById(PwNodeIdUUID(groupUUID))
return getGroupById(NodeIdUUID(groupUUID))
}
// Retrieve recycle bin in index
val recycleBin: PwGroupV4?
get() = getGroupByUUID(recycleBinUUID)
val recycleBin: GroupKDBX?
get() = if (recycleBinUUID == UUID_ZERO) null else getGroupByUUID(recycleBinUUID)
val lastSelectedGroup: PwGroupV4?
val lastSelectedGroup: GroupKDBX?
get() = getGroupByUUID(lastSelectedGroupUUID)
val lastTopVisibleGroup: PwGroupV4?
val lastTopVisibleGroup: GroupKDBX?
get() = getGroupByUUID(lastTopVisibleGroupUUID)
fun setDataEngine(dataEngine: CipherEngine) {
this.dataEngine = dataEngine
}
fun getCustomIcons(): List<PwIconCustom> {
fun getCustomIcons(): List<IconImageCustom> {
return customIcons
}
fun addCustomIcon(customIcon: PwIconCustom) {
fun addCustomIcon(customIcon: IconImageCustom) {
this.customIcons.add(customIcon)
}
@@ -264,7 +322,7 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
fun makeFinalKey(masterSeed: ByteArray) {
kdfParameters?.let { keyDerivationFunctionParameters ->
val kdfEngine = getEngineV4(keyDerivationFunctionParameters)
val kdfEngine = getEngineKDBX4(keyDerivationFunctionParameters)
var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters)
if (transformedMasterKey.size != 32) {
@@ -326,7 +384,7 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
val text = children2.item(k)
if (text.nodeType == Node.TEXT_NODE) {
val txt = text as Text
return Base64Coder.decode(txt.nodeValue)
return Base64.decode(txt.nodeValue, BASE_64_FLAG)
}
}
}
@@ -340,42 +398,42 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
return null
}
override fun newGroupId(): PwNodeIdUUID {
var newId: PwNodeIdUUID
override fun newGroupId(): NodeIdUUID {
var newId: NodeIdUUID
do {
newId = PwNodeIdUUID()
newId = NodeIdUUID()
} while (isGroupIdUsed(newId))
return newId
}
override fun newEntryId(): PwNodeIdUUID {
var newId: PwNodeIdUUID
override fun newEntryId(): NodeIdUUID {
var newId: NodeIdUUID
do {
newId = PwNodeIdUUID()
newId = NodeIdUUID()
} while (isEntryIdUsed(newId))
return newId
}
override fun createGroup(): PwGroupV4 {
return PwGroupV4()
override fun createGroup(): GroupKDBX {
return GroupKDBX()
}
override fun createEntry(): PwEntryV4 {
return PwEntryV4()
override fun createEntry(): EntryKDBX {
return EntryKDBX()
}
override fun rootCanContainsEntry(): Boolean {
return true
}
override fun isBackup(group: PwGroupV4): Boolean {
override fun isInRecycleBin(group: GroupKDBX): Boolean {
// To keep compatibility with old V1 databases
var currentGroup: PwGroupV4? = group
var currentGroup: GroupKDBX? = group
while (currentGroup != null) {
if (currentGroup.parent == rootGroup
&& currentGroup.title.equals("Backup", ignoreCase = true)) {
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
return true
}
currentGroup = currentGroup.parent
@@ -393,7 +451,7 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
* Ensure that the recycle bin tree exists, if enabled and create it
* if it doesn't exist
*/
private fun ensureRecycleBin(resources: Resources) {
fun ensureRecycleBinExists(resources: Resources) {
if (recycleBin == null) {
// Create recycle bin
val recycleBinGroup = createGroup().apply {
@@ -409,61 +467,68 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
}
}
fun removeRecycleBin() {
if (recycleBin != null) {
recycleBinUUID = UUID_ZERO
recycleBinChanged = DateInstant().date
}
}
/**
* Define if a Node must be delete or recycle when remove action is called
* @param node Node to remove
* @return true if node can be recycle, false elsewhere
*/
fun canRecycle(node: PwNode<*, PwGroupV4, PwEntryV4>): Boolean {
fun canRecycle(node: NodeVersioned<*, GroupKDBX, EntryKDBX>): Boolean {
if (!isRecycleBinEnabled)
return false
if (recycleBin == null)
return true
return false
if (!node.isContainedIn(recycleBin!!))
return true
return false
}
fun recycle(group: PwGroupV4, resources: Resources) {
ensureRecycleBin(resources)
fun recycle(group: GroupKDBX, resources: Resources) {
ensureRecycleBinExists(resources)
removeGroupFrom(group, group.parent)
addGroupTo(group, recycleBin)
group.afterAssignNewParent()
}
fun recycle(entry: PwEntryV4, resources: Resources) {
ensureRecycleBin(resources)
fun recycle(entry: EntryKDBX, resources: Resources) {
ensureRecycleBinExists(resources)
removeEntryFrom(entry, entry.parent)
addEntryTo(entry, recycleBin)
entry.afterAssignNewParent()
}
fun undoRecycle(group: PwGroupV4, origParent: PwGroupV4) {
fun undoRecycle(group: GroupKDBX, origParent: GroupKDBX) {
removeGroupFrom(group, recycleBin)
addGroupTo(group, origParent)
}
fun undoRecycle(entry: PwEntryV4, origParent: PwGroupV4) {
fun undoRecycle(entry: EntryKDBX, origParent: GroupKDBX) {
removeEntryFrom(entry, recycleBin)
addEntryTo(entry, origParent)
}
fun getDeletedObjects(): List<PwDeletedObject> {
fun getDeletedObjects(): List<DeletedObject> {
return deletedObjects
}
fun addDeletedObject(deletedObject: PwDeletedObject) {
fun addDeletedObject(deletedObject: DeletedObject) {
this.deletedObjects.add(deletedObject)
}
override fun removeEntryFrom(entryToRemove: PwEntryV4, parent: PwGroupV4?) {
override fun removeEntryFrom(entryToRemove: EntryKDBX, parent: GroupKDBX?) {
super.removeEntryFrom(entryToRemove, parent)
deletedObjects.add(PwDeletedObject(entryToRemove.id))
deletedObjects.add(DeletedObject(entryToRemove.id))
}
override fun undoDeleteEntryFrom(entry: PwEntryV4, origParent: PwGroupV4?) {
override fun undoDeleteEntryFrom(entry: EntryKDBX, origParent: GroupKDBX?) {
super.undoDeleteEntryFrom(entry, origParent)
deletedObjects.remove(PwDeletedObject(entry.id))
deletedObjects.remove(DeletedObject(entry.id))
}
fun containsPublicCustomData(): Boolean {
@@ -477,12 +542,16 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
}
override fun clearCache() {
try {
super.clearCache()
binPool.clear()
binaryPool.clear()
} catch (e: Exception) {
Log.e(TAG, "Unable to clear cache", e)
}
}
companion object {
private val TAG = PwDatabaseV4::class.java.name
private val TAG = DatabaseKDBX::class.java.name
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
private const val DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited
@@ -492,5 +561,9 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
//private const val VersionElementName = "Version";
private const val KeyElementName = "Key"
private const val KeyDataElementName = "Data"
const val BASE_64_FLAG = Base64.NO_WRAP
const val BUFFER_SIZE_BYTES = 3 * 128
}
}

View File

@@ -17,26 +17,32 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException
import com.kunzisoft.keepass.database.exception.LoadDatabaseKeyFileEmptyException
import com.kunzisoft.keepass.utils.MemoryUtil
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException
import org.apache.commons.io.IOUtils
import java.io.*
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
abstract class PwDatabase<
abstract class DatabaseVersioned<
GroupId,
EntryId,
Group : PwGroup<GroupId, EntryId, Group, Entry>,
Entry : PwEntry<GroupId, EntryId, Group, Entry>
Group : GroupVersioned<GroupId, EntryId, Group, Entry>,
Entry : EntryVersioned<GroupId, EntryId, Group, Entry>
> {
// Algorithm used to encrypt the database
protected var algorithm: PwEncryptionAlgorithm? = null
protected var algorithm: EncryptionAlgorithm? = null
abstract val kdfEngine: KdfEngine?
@@ -46,13 +52,13 @@ abstract class PwDatabase<
var finalKey: ByteArray? = null
protected set
var iconFactory = PwIconFactory()
var iconFactory = IconImageFactory()
protected set
var changeDuplicateId = false
private var groupIndexes = LinkedHashMap<PwNodeId<GroupId>, Group>()
private var entryIndexes = LinkedHashMap<PwNodeId<EntryId>, Entry>()
private var groupIndexes = LinkedHashMap<NodeId<GroupId>, Group>()
private var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
abstract val version: String
@@ -60,15 +66,15 @@ abstract class PwDatabase<
abstract var numberKeyEncryptionRounds: Long
var encryptionAlgorithm: PwEncryptionAlgorithm
var encryptionAlgorithm: EncryptionAlgorithm
get() {
return algorithm ?: PwEncryptionAlgorithm.AESRijndael
return algorithm ?: EncryptionAlgorithm.AESRijndael
}
set(algorithm) {
this.algorithm = algorithm
}
abstract val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
var rootGroup: Group? = null
@@ -124,7 +130,7 @@ abstract class PwDatabase<
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyByteArrayOutputStream = ByteArrayOutputStream()
MemoryUtil.copyStream(keyInputStream, keyByteArrayOutputStream)
IOUtils.copy(keyInputStream, keyByteArrayOutputStream)
val keyData = keyByteArrayOutputStream.toByteArray()
val keyByteArrayInputStream = ByteArrayInputStream(keyData)
@@ -134,7 +140,7 @@ abstract class PwDatabase<
}
when (keyData.size.toLong()) {
0L -> throw LoadDatabaseKeyFileEmptyException()
0L -> throw KeyFileEmptyDatabaseException()
32L -> return keyData
64L -> try {
return hexStringToByteArray(String(keyData))
@@ -192,9 +198,9 @@ abstract class PwDatabase<
* -------------------------------------
*/
abstract fun newGroupId(): PwNodeId<GroupId>
abstract fun newGroupId(): NodeId<GroupId>
abstract fun newEntryId(): PwNodeId<EntryId>
abstract fun newEntryId(): NodeId<EntryId>
abstract fun createGroup(): Group
@@ -219,7 +225,7 @@ abstract class PwDatabase<
* ID number to check for
* @return True if the ID is used, false otherwise
*/
fun isGroupIdUsed(id: PwNodeId<GroupId>): Boolean {
fun isGroupIdUsed(id: NodeId<GroupId>): Boolean {
return groupIndexes.containsKey(id)
}
@@ -234,7 +240,7 @@ abstract class PwDatabase<
}
}
fun getGroupById(id: PwNodeId<GroupId>): Group? {
fun getGroupById(id: NodeId<GroupId>): Group? {
return this.groupIndexes[id]
}
@@ -247,7 +253,7 @@ abstract class PwDatabase<
group.parent?.addChildGroup(group)
this.groupIndexes[newGroupId] = group
} else {
throw LoadDatabaseDuplicateUuidException(Type.GROUP, groupId)
throw DuplicateUuidDatabaseException(Type.GROUP, groupId)
}
} else {
this.groupIndexes[groupId] = group
@@ -275,7 +281,7 @@ abstract class PwDatabase<
}
}
fun isEntryIdUsed(id: PwNodeId<EntryId>): Boolean {
fun isEntryIdUsed(id: NodeId<EntryId>): Boolean {
return entryIndexes.containsKey(id)
}
@@ -283,7 +289,7 @@ abstract class PwDatabase<
return entryIndexes.values
}
fun getEntryById(id: PwNodeId<EntryId>): Entry? {
fun getEntryById(id: NodeId<EntryId>): Entry? {
return this.entryIndexes[id]
}
@@ -296,7 +302,7 @@ abstract class PwDatabase<
entry.parent?.addChildEntry(entry)
this.entryIndexes[newEntryId] = entry
} else {
throw LoadDatabaseDuplicateUuidException(Type.ENTRY, entryId)
throw DuplicateUuidDatabaseException(Type.ENTRY, entryId)
}
} else {
this.entryIndexes[entryId] = entry
@@ -376,19 +382,19 @@ abstract class PwDatabase<
addEntryTo(entry, origParent)
}
abstract fun isBackup(group: Group): Boolean
abstract fun isInRecycleBin(group: Group): Boolean
fun isGroupSearchable(group: Group?, omitBackup: Boolean): Boolean {
if (group == null)
return false
if (omitBackup && isBackup(group))
if (omitBackup && isInRecycleBin(group))
return false
return true
}
companion object {
private const val TAG = "PwDatabase"
private const val TAG = "DatabaseVersioned"
val UUID_ZERO = UUID(0, 0)

View File

@@ -17,12 +17,12 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.MemoryUtil
import com.kunzisoft.keepass.utils.ParcelableUtil
import java.util.HashMap
@@ -48,7 +48,7 @@ class AutoType : Parcelable {
this.enabled = parcel.readByte().toInt() != 0
this.obfuscationOptions = parcel.readLong()
this.defaultSequence = parcel.readString() ?: defaultSequence
this.windowSeqPairs = MemoryUtil.readStringParcelableMap(parcel)
this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel)
}
override fun describeContents(): Int {
@@ -59,7 +59,7 @@ class AutoType : Parcelable {
dest.writeByte((if (enabled) 1 else 0).toByte())
dest.writeLong(obfuscationOptions)
dest.writeString(defaultSequence)
MemoryUtil.writeStringParcelableMap(dest, windowSeqPairs)
ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs)
}
fun put(key: String, value: String) {

View File

@@ -0,0 +1,157 @@
/*
* 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.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import java.util.*
/**
* Structure containing information about one entry.
*
* <PRE>
* One entry: [FIELDTYPE(FT)][FIELDSIZE(FS)][FIELDDATA(FD)]
* [FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)]...
*
* [ 2 bytes] FIELDTYPE
* [ 4 bytes] FIELDSIZE, size of FIELDDATA in bytes
* [ n bytes] FIELDDATA, n = FIELDSIZE
*
* Notes:
* - Strings are stored in UTF-8 encoded form and are null-terminated.
* - FIELDTYPE can be one of the FT_ constants.
</PRE> *
*
* @author Naomaru Itoi <nao></nao>@phoneid.org>
* @author Bill Zwicky <wrzwicky></wrzwicky>@pobox.com>
* @author Dominik Reichl <dominik.reichl></dominik.reichl>@t-online.de>
* @author Jeremy Jamet <jeremy.jamet></jeremy.jamet>@kunzisoft.com>
*/
class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
/** A string describing what is in binaryData */
var binaryDescription = ""
var binaryData: BinaryAttachment? = null
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
get() {
if (notes.isEmpty()) return false
if (binaryDescription != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
if (title != PMS_ID_TITLE) return false
if (username.isEmpty()) return false
if (username != PMS_ID_USER) return false
if (url.isEmpty()) return false
return if (url != PMS_ID_URL) false else icon.isMetaStreamIcon
}
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
}
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
}
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
title = parcel.readString() ?: title
username = parcel.readString() ?: username
password = parcel.readString() ?: password
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
binaryDescription = parcel.readString() ?: binaryDescription
binaryData = parcel.readParcelable(BinaryAttachment::class.java.classLoader)
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
return parcel.readParcelable(GroupKDB::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDB?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(title)
dest.writeString(username)
dest.writeString(password)
dest.writeString(url)
dest.writeString(notes)
dest.writeString(binaryDescription)
dest.writeParcelable(binaryData, flags)
}
fun updateWith(source: EntryKDB) {
super.updateWith(source)
title = source.title
username = source.username
password = source.password
url = source.url
notes = source.notes
binaryDescription = source.binaryDescription
binaryData = source.binaryData
}
override var username = ""
/**
* @return the actual password byte array.
*/
override var password = ""
override var url = ""
override var notes = ""
override var title = ""
override val type: Type
get() = Type.ENTRY
companion object {
/** Size of byte buffer needed to hold this struct. */
private const val PMS_ID_BINDESC = "bin-stream"
private const val PMS_ID_TITLE = "Meta-Info"
private const val PMS_ID_USER = "SYSTEM"
private const val PMS_ID_URL = "$"
@JvmField
val CREATOR: Parcelable.Creator<EntryKDB> = object : Parcelable.Creator<EntryKDB> {
override fun createFromParcel(parcel: Parcel): EntryKDB {
return EntryKDB(parcel)
}
override fun newArray(size: Int): Array<EntryKDB?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -17,24 +17,34 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.ProtectedBinary
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.MemoryUtil
import com.kunzisoft.keepass.utils.ParcelableUtil
import java.util.*
class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
// To decode each field not parcelable
@Transient
private var mDatabase: PwDatabaseV4? = null
private var mDatabase: DatabaseKDBX? = null
@Transient
private var mDecodeRef = false
override var icon: PwIcon
override var icon: IconImage
get() {
return when {
iconCustom.isUnknown -> super.icon
@@ -42,19 +52,19 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
}
}
set(value) {
if (value is PwIconStandard)
iconCustom = PwIconCustom.UNKNOWN_ICON
if (value is IconImageStandard)
iconCustom = IconImageCustom.UNKNOWN_ICON
super.icon = value
}
var iconCustom = PwIconCustom.UNKNOWN_ICON
var iconCustom = IconImageCustom.UNKNOWN_ICON
private var customData = HashMap<String, String>()
var fields = HashMap<String, ProtectedString>()
val binaries = HashMap<String, ProtectedBinary>()
var binaries = HashMap<String, BinaryAttachment>()
var foregroundColor = ""
var backgroundColor = ""
var overrideURL = ""
var autoType = AutoType()
var history = ArrayList<PwEntryV4>()
var history = ArrayList<EntryKDBX>()
var additional = ""
var tags = ""
@@ -93,12 +103,12 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(PwIconCustom::class.java.classLoader) ?: iconCustom
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
usageCount = parcel.readLong()
locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged
customData = MemoryUtil.readStringParcelableMap(parcel)
fields = MemoryUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
// TODO binaries = MemoryUtil.readStringParcelableMap(parcel, ProtectedBinary.class);
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel)
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java)
foregroundColor = parcel.readString() ?: foregroundColor
backgroundColor = parcel.readString() ?: backgroundColor
overrideURL = parcel.readString() ?: overrideURL
@@ -114,9 +124,9 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount)
dest.writeParcelable(locationChanged, flags)
MemoryUtil.writeStringParcelableMap(dest, customData)
MemoryUtil.writeStringParcelableMap(dest, flags, fields)
// TODO MemoryUtil.writeStringParcelableMap(dest, flags, binaries);
ParcelableUtil.writeStringParcelableMap(dest, customData)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
ParcelableUtil.writeStringParcelableMap(dest, flags, binaries)
dest.writeString(foregroundColor)
dest.writeString(backgroundColor)
dest.writeString(overrideURL)
@@ -131,11 +141,11 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
* Update with deep copy of each entry element
* @param source
*/
fun updateWith(source: PwEntryV4, copyHistory: Boolean = true) {
fun updateWith(source: EntryKDBX, copyHistory: Boolean = true) {
super.updateWith(source)
iconCustom = PwIconCustom(source.iconCustom)
iconCustom = IconImageCustom(source.iconCustom)
usageCount = source.usageCount
locationChanged = PwDate(source.locationChanged)
locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map
customData.clear()
customData.putAll(source.customData)
@@ -155,7 +165,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
tags = source.tags
}
fun startToManageFieldReferences(db: PwDatabaseV4) {
fun startToManageFieldReferences(db: DatabaseKDBX) {
this.mDatabase = db
this.mDecodeRef = true
}
@@ -165,24 +175,24 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
this.mDecodeRef = false
}
override fun initNodeId(): PwNodeId<UUID> {
return PwNodeIdUUID()
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
}
override fun copyNodeId(nodeId: PwNodeId<UUID>): PwNodeId<UUID> {
return PwNodeIdUUID(nodeId.id)
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
}
override fun readParentParcelable(parcel: Parcel): PwGroupV4? {
return parcel.readParcelable(PwGroupV4::class.java.classLoader)
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: PwGroupV4?, parcel: Parcel, flags: Int) {
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
/**
* Decode a reference key with the SprEngineV4
* Decode a reference key with the FieldReferencesEngine
* @param decodeRef
* @param key
* @return
@@ -190,7 +200,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
private fun decodeRefKey(decodeRef: Boolean, key: String): String {
return fields[key]?.toString()?.let { text ->
return if (decodeRef) {
if (mDatabase == null) text else SprEngineV4().compile(text, this, mDatabase!!)
if (mDatabase == null) text else FieldReferencesEngine().compile(text, this, mDatabase!!)
} else text
} ?: ""
}
@@ -235,10 +245,10 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
override var usageCount: Long = 0
override var locationChanged = PwDate()
override var locationChanged = DateInstant()
fun afterChangeParent() {
locationChanged = PwDate()
locationChanged = DateInstant()
}
private fun isStandardField(key: String): Boolean {
@@ -274,7 +284,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
fields[label] = value
}
fun putProtectedBinary(key: String, value: ProtectedBinary) {
fun putProtectedBinary(key: String, value: BinaryAttachment) {
binaries[key] = value
}
@@ -290,10 +300,14 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
return customData.isNotEmpty()
}
fun addEntryToHistory(entry: PwEntryV4) {
fun addEntryToHistory(entry: EntryKDBX) {
history.add(entry)
}
fun removeEntryFromHistory(position: Int) {
history.removeAt(position)
}
fun removeAllHistory() {
history.clear()
}
@@ -330,12 +344,12 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
const val STR_NOTES = "Notes"
@JvmField
val CREATOR: Parcelable.Creator<PwEntryV4> = object : Parcelable.Creator<PwEntryV4> {
override fun createFromParcel(parcel: Parcel): PwEntryV4 {
return PwEntryV4(parcel)
val CREATOR: Parcelable.Creator<EntryKDBX> = object : Parcelable.Creator<EntryKDBX> {
override fun createFromParcel(parcel: Parcel): EntryKDBX {
return EntryKDBX(parcel)
}
override fun newArray(size: Int): Array<PwEntryV4?> {
override fun newArray(size: Int): Array<EntryKDBX?> {
return arrayOfNulls(size)
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.element.entry
import android.os.Parcel
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.node.NodeVersioned
abstract class EntryVersioned
<
GroupId,
EntryId,
ParentGroup: GroupVersioned<GroupId, EntryId, ParentGroup, Entry>,
Entry: EntryVersioned<GroupId, EntryId, ParentGroup, Entry>
>
: NodeVersioned<EntryId, ParentGroup, Entry>, EntryVersionedInterface<ParentGroup> {
constructor() : super()
constructor(parcel: Parcel) : super(parcel)
}

View File

@@ -0,0 +1,33 @@
/*
* 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.element.entry
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
interface EntryVersionedInterface<ParentGroup> : NodeVersionedInterface<ParentGroup> {
var username: String
var password: String
var url: String
var notes: String
}

View File

@@ -17,24 +17,26 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
package com.kunzisoft.keepass.database.element.entry
import com.kunzisoft.keepass.database.search.EntrySearchHandlerV4
import com.kunzisoft.keepass.database.search.SearchParametersV4
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler
import com.kunzisoft.keepass.database.search.SearchParametersKDBX
import com.kunzisoft.keepass.utils.StringUtil
import java.util.*
class SprEngineV4 {
class FieldReferencesEngine {
inner class TargetResult(var entry: PwEntryV4?, var wanted: Char)
inner class TargetResult(var entry: EntryKDBX?, var wanted: Char)
private inner class SprContextV4 {
var databaseV4: PwDatabaseV4? = null
var entry: PwEntryV4
var databaseV4: DatabaseKDBX? = null
var entry: EntryKDBX
var refsCache: MutableMap<String, String> = HashMap()
internal constructor(db: PwDatabaseV4, entry: PwEntryV4) {
internal constructor(db: DatabaseKDBX, entry: EntryKDBX) {
this.databaseV4 = db
this.entry = entry
}
@@ -46,7 +48,7 @@ class SprEngineV4 {
}
}
fun compile(text: String, entry: PwEntryV4, database: PwDatabaseV4): String {
fun compile(text: String, entry: EntryKDBX, database: DatabaseKDBX): String {
return compileInternal(text, SprContextV4(database, entry), 0)
}
@@ -139,7 +141,7 @@ class SprEngineV4 {
val scan = Character.toUpperCase(ref[2])
val wanted = Character.toUpperCase(ref[0])
val searchParametersV4 = SearchParametersV4()
val searchParametersV4 = SearchParametersKDBX()
searchParametersV4.setupNone()
searchParametersV4.searchString = ref.substring(4)
@@ -161,7 +163,7 @@ class SprEngineV4 {
return null
}
val list = ArrayList<PwEntryV4>()
val list = ArrayList<EntryKDBX>()
// TODO type parameter
searchEntries(contextV4.databaseV4!!.rootGroup, searchParametersV4, list)
@@ -195,7 +197,7 @@ class SprEngineV4 {
return newText
}
private fun searchEntries(root: PwGroupV4?, searchParametersV4: SearchParametersV4?, listStorage: MutableList<PwEntryV4>?) {
private fun searchEntries(root: GroupKDBX?, searchParametersV4: SearchParametersKDBX?, listStorage: MutableList<EntryKDBX>?) {
if (searchParametersV4 == null) {
return
}
@@ -205,7 +207,7 @@ class SprEngineV4 {
val terms = StringUtil.splitStringTerms(searchParametersV4.searchString)
if (terms.size <= 1 || searchParametersV4.regularExpression) {
root!!.doForEachChild(EntrySearchHandlerV4(searchParametersV4, listStorage), null)
root!!.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, listStorage), null)
return
}
@@ -214,9 +216,9 @@ class SprEngineV4 {
Collections.sort(terms, stringLengthComparator)
val fullSearch = searchParametersV4.searchString
var childEntries: List<PwEntryV4>? = root!!.getChildEntries()
var childEntries: List<EntryKDBX>? = root!!.getChildEntries()
for (i in terms.indices) {
val pgNew = ArrayList<PwEntryV4>()
val pgNew = ArrayList<EntryKDBX>()
searchParametersV4.searchString = terms[i]
@@ -226,12 +228,12 @@ class SprEngineV4 {
negate = searchParametersV4.searchString.isNotEmpty()
}
if (!root.doForEachChild(EntrySearchHandlerV4(searchParametersV4, pgNew), null)) {
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, pgNew), null)) {
childEntries = null
break
}
val complement = ArrayList<PwEntryV4>()
val complement = ArrayList<EntryKDBX>()
if (negate) {
for (entry in childEntries!!) {
if (!pgNew.contains(entry)) {

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