Compare commits

...

918 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
J-Jamet
cb5ca575d5 Merge branch 'release/2.5.0.0beta24' 2019-11-12 12:35:16 +01:00
J-Jamet
f4caaad9ee Fix invalid_db_same_uuid error 2019-11-12 12:09:58 +01:00
J-Jamet
b9cfb32a20 Fix OTP dialog 2019-11-12 11:56:19 +01:00
J-Jamet
095e5e5dd6 Add FLAG_GRANT_PREFIX_URI_PERMISSION flag 2019-11-12 10:43:32 +01:00
J-Jamet
ffc58688d8 Upgrade Gradle 2019-11-12 09:38:36 +01:00
Allan Nordhøy
6a69a7f416 Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.5% (360 of 381 strings)

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

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

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

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

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

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

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

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-10-25 13:53:16 +02:00
J-Jamet
ef7387f2f3 Remove entry notifications after killing app (by swap) 2019-10-25 10:08:39 +02:00
J-Jamet
f774298587 Update CHANGELOG 2019-10-24 15:23:52 +02:00
J-Jamet
f8134307f6 Fix long click in paste mode 2019-10-24 13:10:52 +02:00
J-Jamet
fe461f2e7c Fix action error 2019-10-24 12:36:22 +02:00
J-Jamet
023c841747 Better code 2019-10-24 11:49:23 +02:00
J-Jamet
af95c0903a Better node error implementation 2019-10-24 11:29:43 +02:00
J-Jamet
0d756db8aa Beter code 2019-10-24 11:07:58 +02:00
J-Jamet
2c5dcc9b11 Fix back for action selection mode 2019-10-24 10:25:21 +02:00
Éfrit
21c6ea73b2 Translated using Weblate (French)
Currently translated at 100.0% (381 of 381 strings)

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-10-22 23:53:06 +02:00
J-Jamet
87760ab4f6 Fix save database and finish 2019-10-22 15:02:51 +02:00
J-Jamet
88ebe58a88 Fix action node 2019-10-22 14:46:13 +02:00
villabunterkunt
fb023b81b5 Translated using Weblate (German)
Currently translated at 97.9% (373 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-10-21 21:02:16 +02:00
J-Jamet
2de6bbc6c0 Fix selected color 2019-10-21 17:12:40 +02:00
J-Jamet
4ef436629d Merge branch 'feature/Loading_Database' into develop 2019-10-21 11:09:37 +02:00
Allan Nordhøy
9fd342f1e7 Translated using Weblate (Norwegian Bokmål)
Currently translated at 89.5% (341 of 381 strings)

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-10-19 10:52:58 +02:00
J-Jamet
9d160db281 Add setting updates for dialog thread 2019-10-18 13:44:28 +02:00
J-Jamet
2e58c2f1b3 Fix duplication during deletion 2019-10-17 20:02:49 +02:00
J-Jamet
d1d2b99e09 Fix autofill selection 2019-10-17 15:17:18 +02:00
J-Jamet
def9744f75 Fix launch group activity when database loaded 2019-10-17 14:54:34 +02:00
J-Jamet
214e2cf109 Better database loading code 2019-10-17 14:07:39 +02:00
J-Jamet
b25180c617 Show update message and fix dialog retrievment 2019-10-17 11:29:30 +02:00
J-Jamet
6a5263df77 Fix biometric recognition error with new loading process 2019-10-17 10:28:24 +02:00
J-Jamet
2982f67717 Fix biometric recognition with new loading process 2019-10-17 10:24:12 +02:00
J-Jamet
b559670dff Fix result security by binder 2019-10-17 09:53:00 +02:00
J-Jamet
891d3142d2 Better code for bind callback 2019-10-16 18:49:15 +02:00
J-Jamet
2637788429 Fix dialog update 2019-10-16 15:31:07 +02:00
J-Jamet
a21de3b892 Fix update group 2019-10-16 11:48:35 +02:00
J-Jamet
e087e19120 Fix update entry 2019-10-16 10:46:56 +02:00
J-Jamet
721d61dda7 Fix update nodes 2019-10-15 18:05:33 +02:00
J-Jamet
e0e7e431cf Best update and nodeId type implementation 2019-10-15 17:02:59 +02:00
J-Jamet
93948e7c61 First commit for async loading 2019-10-15 16:01:50 +02:00
J-Jamet
b150c718a0 Fix edit notes field limited to 4 lines 2019-10-14 10:17:48 +02:00
J-Jamet
a71e4c3902 Add language parameter for issue template 2019-10-14 09:57:06 +02:00
Adolfo Jayme Barrientos
b7dc13d863 Translated using Weblate (Spanish)
Currently translated at 89.8% (342 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2019-10-12 16:52:53 +02:00
J-Jamet
1e3c58e359 Fix clipboard LOCK_DATABASE signal #366 2019-10-11 12:52:43 +02:00
J-Jamet
5f75599e9f Replace notification icons by PNG #364 2019-10-11 11:50:16 +02:00
J-Jamet
b602f9b77d Add icon for paste action 2019-10-11 10:58:21 +02:00
J-Jamet
aa948c1ece Remove null for BiometricUnlockCallback 2019-10-11 10:55:05 +02:00
J-Jamet
e599a51152 Refactor prompt build code 2019-10-11 10:41:45 +02:00
J-Jamet
ee6052f4d1 Update CHANGELOGS 2019-10-11 10:21:04 +02:00
J-Jamet
eba527f477 Add unrecoverable key exception 2019-10-11 10:18:28 +02:00
J-Jamet
09e0d6d3cc Update biometric and room lib 2019-10-11 10:03:44 +02:00
J-Jamet
9aefc984be Merge branch 'feature/Multiple_Node_Action' into develop #275 2019-10-10 19:19:08 +02:00
J-Jamet
2ce3b21f1b Add number of selected action nodes 2019-10-10 19:17:38 +02:00
J-Jamet
4d2f3cb4b1 Remove unused key for orientation change 2019-10-10 19:04:24 +02:00
J-Jamet
e62b46c4c0 Fix open and edit action bar 2019-10-10 18:38:15 +02:00
J-Jamet
6472601170 Add Toolbar action animation 2019-10-10 18:34:05 +02:00
J-Jamet
89dd7bfefb Fix finish node action when exit activity 2019-10-10 17:59:23 +02:00
J-Jamet
fb2ea4c0ed Multiple action node for Move Copy and Delete 2019-10-10 17:53:35 +02:00
J-Jamet
8d84358d48 Refactor code 2019-10-10 12:49:16 +02:00
J-Jamet
1d8661c633 Better node action refresh 2019-10-09 21:19:10 +02:00
J-Jamet
48130eee45 Change node action menu order 2019-10-09 20:23:22 +02:00
J-Jamet
2cf83962fe Merge branch 'develop' into feature/Multiple_Node_Action 2019-10-09 20:20:25 +02:00
J-Jamet
aecf7c0c39 Fix remove node at 2019-10-09 20:20:10 +02:00
J-Jamet
39606e2676 Custom Toolbar action 2019-10-09 20:16:11 +02:00
Ema Panz
6e00fa2d01 Translated using Weblate (Italian)
Currently translated at 95.5% (364 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2019-10-09 15:58:47 +02:00
J-Jamet
f79aa339e9 Selection by Contextual Menu 2019-10-09 15:58:00 +02:00
J-Jamet
f412fce912 Add safeNextText method to XmlPullParser 2019-10-07 15:51:44 +02:00
J-Jamet
cc20b7503c Refactor exceptions 2019-10-07 15:19:13 +02:00
Jérémy JAMET
2573434763 Update Readme.md
update FAQ
2019-10-07 09:33:21 +02:00
Jérémy JAMET
f153c26fef Set theme jekyll-theme-cayman 2019-10-07 09:15:48 +02:00
Ldm Public
125f461cbe Translated using Weblate (French)
Currently translated at 100.0% (381 of 381 strings)

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

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2019-10-06 16:56:45 +02:00
J-Jamet
ab1fc8c5d5 Remove CONTRIBUTORS file no longer updated, see https://github.com/Kunzisoft/KeePassDX/graphs/contributors 2019-10-06 11:46:35 +02:00
J-Jamet
8477f4ba08 Fix education in PasswordActivity 2019-10-06 11:15:56 +02:00
J-Jamet
e6518ffdc8 Upgrade ISSUE_TEMPLATE 2019-10-06 10:39:30 +02:00
J-Jamet
99917c7f28 Try to prevent XXE #200 2019-10-06 10:25:13 +02:00
J-Jamet
fcc29f67a3 Upgrade CHANGELOGS 2019-10-05 15:46:18 +02:00
J-Jamet
7dd49f050c Highlight expires entries 2019-10-05 15:43:45 +02:00
J-Jamet
5f96de84b0 Manage expires 2019-10-05 15:32:04 +02:00
J-Jamet
54c2f5a61f Fix Minor Accessibility Issues #334 2019-10-05 12:56:09 +02:00
J-Jamet
921c6f88aa Merge branch 'feature/OPEN_DOCUMENT' into develop 2019-10-05 12:43:40 +02:00
J-Jamet
a0cb579df4 Merge branch 'develop' into feature/OPEN_DOCUMENT 2019-10-05 11:47:27 +02:00
J-Jamet
d6a7c34ff3 Update gradle 2019-10-05 11:47:12 +02:00
J-Jamet
bf2e61f149 Merge branch 'develop' into feature/OPEN_DOCUMENT 2019-10-05 11:45:54 +02:00
J-Jamet
eaf5dc5988 Show number of children in toolbar #282 2019-10-05 11:27:34 +02:00
J-Jamet
879ee013db Fix multiple biometric menu #332 2019-10-05 10:12:45 +02:00
J-Jamet
e13d53eae4 Unable default username and custom color to Database V2 2019-10-05 09:22:41 +02:00
J-Jamet
d72c8184c9 Fix databaseV1 settings 2019-10-04 19:00:18 +02:00
Kunzisoft
c4d3c8cbfb Translated using Weblate (French)
Currently translated at 100.0% (381 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-10-04 18:37:57 +02:00
J-Jamet
02a3d85f80 Try to fix "Unable to launch Activity from MagiKeyboard" #348 2019-10-04 16:53:20 +02:00
J-Jamet
19b0722f1f Add KeePass DX version in ISSUE_TEMPLATE 2019-10-04 16:36:20 +02:00
J-Jamet
f14222b192 Merge branch 'develop' into Bug_Feature_Template 2019-10-04 16:22:37 +02:00
J-Jamet
4f4f6d30d9 Change menu biometric order 2019-10-04 16:22:08 +02:00
J-Jamet
fdd329e982 Fix unchecked "Use as default database" doesn't work #354 2019-10-04 13:20:30 +02:00
J-Jamet
55a4d388b3 Fix V1 locked with keyFile and no password #353 2019-10-04 13:15:10 +02:00
J-Jamet
5c6be448ec Upgrade Changelogs 2019-10-04 12:00:22 +02:00
J-Jamet
3e79ddcc21 Merge branch 'feature/Color_Preference' into develop 2019-10-04 11:50:24 +02:00
J-Jamet
5362758424 Disable custom database color 2019-10-04 11:50:11 +02:00
J-Jamet
c10e3df2a7 Enable settings as switch 2019-10-04 11:47:33 +02:00
J-Jamet
166784021a Fix color setting orientation change 2019-10-04 11:44:16 +02:00
Jérémy JAMET
5615c31e08 Update issue templates 2019-10-03 12:04:38 +02:00
J-Jamet
fb60dd5921 Fix Disable color 2019-10-02 19:27:35 +02:00
J-Jamet
ff4c1b779b First code for color preference 2019-10-02 15:38:03 +02:00
J-Jamet
53a7b99567 Fix package for preference 2019-10-01 18:00:41 +02:00
J-Jamet
a57103bafb Add default username and better setter database data implementation 2019-10-01 17:05:46 +02:00
J-Jamet
2540f32dbf Add NDK version 2019-10-01 15:52:46 +02:00
J-Jamet
499ccd6b7c #297 Duplicate UUID 2019-10-01 15:00:35 +02:00
J-Jamet
a4359560b9 Change duplicate UUID dialog message 2019-10-01 14:42:32 +02:00
J-Jamet
149483cc2d Fix duplicate UUID dialog orientation change 2019-10-01 14:33:50 +02:00
J-Jamet
a1d2022492 Fix index for duplicate UUID 2019-10-01 14:15:11 +02:00
J-Jamet
891036c35c Fix database after dialog positive click 2019-10-01 12:35:03 +02:00
J Smith
4e16ba5f56 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (381 of 381 strings)

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

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-10-01 11:56:12 +02:00
J-Jamet
94a9942db5 Show duplicate UUID message and fix cancel string 2019-10-01 11:38:46 +02:00
J-Jamet
5f347fe106 Show same UUID entries 2019-09-29 22:26:47 +02:00
J-Jamet
a34a84ae16 Fix refactoring 2019-09-26 20:57:10 +02:00
J-Jamet
40b0982298 Refactor load database 2019-09-26 20:29:33 +02:00
J-Jamet
4100258476 Remove unused import 2019-09-26 16:26:41 +02:00
J-Jamet
5f3f6661b7 Remove URI verification 2019-09-26 16:24:41 +02:00
J-Jamet
75af97e0ae Better code to open document 2019-09-26 16:04:05 +02:00
J-Jamet
58f158c457 Fix java.lang.IllegalStateException for CoordinatorLayout 2019-09-26 14:02:59 +02:00
J-Jamet
ce27eae1f0 Upgrade biometric lib 2019-09-26 13:20:00 +02:00
WaldiS
0aa0b3e993 Translated using Weblate (Polish)
Currently translated at 96.1% (366 of 381 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-09-25 20:28:03 +02:00
J-Jamet
1cc5a08236 Better delete implementation 2019-09-25 15:45:12 +02:00
J-Jamet
4c587eeb03 Remove unused open link, and expand view lib. Redo toolbar paste animation 2019-09-25 15:25:58 +02:00
J-Jamet
ab70c2d014 Add settings 2019-09-24 15:42:57 +02:00
J-Jamet
8413160ac5 Rename compression algorithm 2019-09-24 13:10:49 +02:00
J-Jamet
5abc403171 Add compression setting 2019-09-24 12:52:07 +02:00
J-Jamet
9b891013b8 Add database settings 2019-09-24 11:52:01 +02:00
J-Jamet
9413987355 Update CHANGELOG 2019-09-23 18:01:40 +02:00
J-Jamet
f95b514b41 Fix orientation change during entry edit 2019-09-23 17:40:53 +02:00
WaldiS
f6985c8944 Translated using Weblate (Polish)
Currently translated at 96.1% (366 of 381 strings)

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

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

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2019-09-23 15:28:01 +02:00
J-Jamet
9c9980bba6 Change TimeOut default to 5 minutes 2019-09-23 11:11:53 +02:00
J-Jamet
2226c15d29 Merge branch 'feature/Entry_History' into develop 2019-09-23 10:30:20 +02:00
J-Jamet
82a859bd9c Better KDF implementation 2019-09-22 20:23:59 +02:00
J-Jamet
83873fab81 Fix custom fields orientation change #337 2019-09-22 18:27:32 +02:00
J-Jamet
31f2be7b91 Hide history preference with database V3 2019-09-22 14:34:52 +02:00
J-Jamet
16458e6646 Add description for max history preferences 2019-09-22 14:12:58 +02:00
J-Jamet
2b9678707d Separate recycle bin preference 2019-09-22 13:54:26 +02:00
J-Jamet
cdbb23d7f1 Separate recycle bin preference 2019-09-22 13:52:30 +02:00
J-Jamet
23fd1b83f4 Better preference implementation 2019-09-22 13:46:10 +02:00
J-Jamet
40b0ebe49b Fix preferences 2019-09-22 13:14:47 +02:00
J-Jamet
7cd8682544 Better Preference implementation 2019-09-20 16:30:05 +02:00
J-Jamet
d0dd478ac8 Add max history preferences 2019-09-19 17:53:32 +02:00
J-Jamet
ffb547c452 Change history string 2019-09-19 15:37:07 +02:00
J-Jamet
bd829f129f Better condition to show history 2019-09-19 14:29:24 +02:00
J-Jamet
5ad3f62de5 Add history after each entry update for DatabaseV4 2019-09-19 14:20:28 +02:00
solokot
b0ec4942bc Translated using Weblate (Russian)
Currently translated at 100.0% (381 of 381 strings)

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-09-19 04:51:11 +02:00
J-Jamet
116643a45a Merge branch 'develop' into feature/Entry_History 2019-09-18 22:33:06 +02:00
J-Jamet
2f0eb283ed Fix selection mode color 2019-09-18 22:30:29 +02:00
J-Jamet
6d46fccdcd Show history by click 2019-09-18 22:14:50 +02:00
J-Jamet
f5dc94bfec Add date string for PwDate and new list view entry history 2019-09-18 12:57:11 +02:00
solokot
94bdb0e3da Translated using Weblate (Russian)
Currently translated at 99.7% (380 of 381 strings)

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

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

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-09-16 22:24:11 +02:00
J-Jamet
a8ccb67a87 Entry history first code 2019-09-15 17:56:25 +02:00
J-Jamet
66051382f1 Upgrade version 2019-09-15 15:56:54 +02:00
J-Jamet
0fb3028c91 Fix group copy 2019-09-15 10:42:10 +02:00
Hosted Weblate
5e5baa4892 Merge branch 'origin/master' into Weblate. 2019-09-15 10:17:14 +02:00
WaldiS
9d1257ed9d Translated using Weblate (Polish)
Currently translated at 97.6% (368 of 377 strings)

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

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-09-15 10:17:07 +02:00
J-Jamet
a1b692abe5 Update fastlane 2019-09-14 12:47:42 +02:00
J-Jamet
4e06842d0f Fix #331 Crash if "Save keyfile" setting is unchecked 2019-09-14 12:47:04 +02:00
J-Jamet
f04c2ee1da Merge tag '2.5.0.0beta23' into develop
2.5.0.0beta23
2019-09-14 11:31:37 +02:00
J-Jamet
9700dbcc3f Merge branch 'release/2.5.0.0beta23' 2019-09-14 11:31:30 +02:00
J-Jamet
4e344458b2 Fix warnings 2019-09-14 11:17:16 +02:00
J-Jamet
6f95cc7296 Merge branch 'translations' into develop 2019-09-14 10:39:55 +02:00
J-Jamet
cd66f8f57e Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2019-09-14 10:39:24 +02:00
J-Jamet
83f0eb9a33 Fix database preferences 2019-09-13 22:20:56 +02:00
J-Jamet
ca89bba768 Update screen image 2019-09-13 21:50:37 +02:00
J-Jamet
2d2bd5013e Upgrade Store Screens 2019-09-13 21:45:55 +02:00
J-Jamet
b93d7bbf41 Better message view for fingerprint problem #114 2019-09-13 19:49:53 +02:00
J-Jamet
000277705a Fix #316 Search stop selection mode 2019-09-13 17:04:33 +02:00
J-Jamet
088712e784 Small log change 2019-09-13 16:02:48 +02:00
J-Jamet
117592387e Change SDK version for icon packs 2019-09-13 15:30:34 +02:00
J-Jamet
76bb1a369c Fix switch to previous keyboard deprecation 2019-09-13 14:39:34 +02:00
J-Jamet
9331c281fe Fix small variables names 2019-09-13 13:44:12 +02:00
J-Jamet
f2666316e1 Better UriUtil methods 2019-09-12 17:07:42 +02:00
Kwangho Kim
7c2ff5067d Translated using Weblate (Korean)
Currently translated at 43.5% (164 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2019-09-12 16:24:01 +02:00
zeritti
5fed641c7c Translated using Weblate (Czech)
Currently translated at 99.7% (376 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2019-09-12 16:24:01 +02:00
Allan Nordhøy
18bd62ee5a Translated using Weblate (English)
Currently translated at 99.7% (376 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-09-12 16:23:56 +02:00
Allan Nordhøy
bc57e6e257 Translated using Weblate (German)
Currently translated at 98.7% (372 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-09-12 16:23:56 +02:00
C. Rüdinger
69b1aba218 Translated using Weblate (German)
Currently translated at 98.7% (372 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-09-12 16:23:56 +02:00
zeritti
df04d998c2 Translated using Weblate (German)
Currently translated at 98.7% (372 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-09-12 16:23:55 +02:00
Mesut Akcan
e986fe5f60 Translated using Weblate (Turkish)
Currently translated at 99.7% (376 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-09-12 16:23:54 +02:00
J-Jamet
e4ac0ee258 Fix seekbar value 2019-09-12 14:39:27 +02:00
J-Jamet
426aa0e7da Better StringUtil code 2019-09-12 14:19:14 +02:00
J-Jamet
6c0a48af48 Remove unused Tests 2019-09-12 14:15:34 +02:00
J-Jamet
d865da1613 Fix small issue 2019-09-12 13:49:14 +02:00
J-Jamet
6fa4c1e06e Fix warnings 2019-09-12 12:39:45 +02:00
J-Jamet
52a6b3e046 Upgrade Preference library 2019-09-12 12:39:27 +02:00
J-Jamet
fa26d2f938 Fix mini fab color 2019-09-12 11:52:43 +02:00
J-Jamet
4777cdc7ae Fix file_modification view 2019-09-12 11:28:12 +02:00
J-Jamet
cd8d3cbf6a Fix margins 2019-09-12 10:52:04 +02:00
J-Jamet
e6a6feb5c0 Fix margins 2019-09-12 10:46:35 +02:00
J-Jamet
98d6fb9214 Remove file_manager links 2019-09-12 10:41:31 +02:00
J-Jamet
94244cd15b Fix #242 Null AutoType 2019-09-11 18:03:03 +02:00
J-Jamet
c5e2ca9907 Fix #327 save to previous database version when conditions are required 2019-09-11 16:54:58 +02:00
Kwangho Kim
e4fef44caf Translated using Weblate (English)
Currently translated at 99.7% (376 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-09-11 15:59:08 +02:00
J-Jamet
5bd9da9bb1 Small change in ic_launcher 2019-09-10 16:31:37 +02:00
J-Jamet
5f0e899679 Add TODO for history 2019-09-10 16:26:17 +02:00
J-Jamet
4b1806900b Fix Broadcast Receiver don't working in Android 9 2019-09-10 16:26:02 +02:00
J-Jamet
30e2912885 Remove unused code 2019-09-10 16:19:52 +02:00
J-Jamet
fc3608ff69 Refactor startActivityForEntrySelection 2019-09-10 16:11:20 +02:00
J-Jamet
6de47ec9b2 Fix small bug 2019-09-10 16:06:43 +02:00
J-Jamet
f7253764a2 Multiline to password generator and up to 128 chars max 2019-09-10 12:53:03 +02:00
J-Jamet
c54b134c31 Masked password in generator #324 2019-09-10 12:43:29 +02:00
J-Jamet
a6bdca52be Fix Search shows Backup entries #320 2019-09-10 12:33:59 +02:00
J-Jamet
29eec05f8f Fix warning with parcelable 2019-09-09 13:35:29 +02:00
J-Jamet
250aef9738 ImageView Key Generator as Button 2019-09-09 12:30:58 +02:00
J-Jamet
50097914a2 Let Copy UUID 2019-09-09 11:59:26 +02:00
J-Jamet
ecff4fb2c5 Update CHANGELOG 2019-09-09 11:00:19 +02:00
J-Jamet
ab3d17f352 Fix OOM #256 2019-09-09 10:55:34 +02:00
J-Jamet
a75d237e53 Kotlinized MemUtil 2019-09-09 10:11:16 +02:00
Allan Nordhøy
a0c7786e1b Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.6% (353 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-09-06 21:24:59 +02:00
J-Jamet
0d32c38c79 Fix custom fields hidden 2019-09-06 14:06:51 +02:00
J-Jamet
b846eda410 Update CHANGELOG 2019-09-06 12:44:06 +02:00
J-Jamet
7c33c9ec02 Better custom fields implementation, fix references 2019-09-06 12:39:27 +02:00
J-Jamet
74c08340a6 Fix field references #61 2019-09-06 11:10:04 +02:00
J-Jamet
82161536be Add DE translations 2019-09-05 15:42:18 +02:00
J-Jamet
752dbca356 Update CHANGELOG 2019-09-05 15:40:34 +02:00
J-Jamet
9ef56f6fd8 Register fingerprint only if database is open #322 2019-09-05 14:56:07 +02:00
J-Jamet
0239f115ae Fix show biometric view issue 2019-09-05 13:11:48 +02:00
J-Jamet
9775e09221 Fix small issue 2019-09-04 14:46:02 +02:00
J-Jamet
c975a1bfc0 Fix small issues 2019-09-04 14:33:31 +02:00
J-Jamet
c263536078 Change icon background for custom image 2019-09-04 14:02:55 +02:00
J-Jamet
b559eeaad0 Merge branch 'feature/Biometric' into develop 2019-09-04 13:17:59 +02:00
J-Jamet
63373083ab Store biometric cipher values in database 2019-09-04 13:17:33 +02:00
Swann Martinet
bf525807b0 Translated using Weblate (French)
Currently translated at 100.0% (377 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-09-04 07:25:10 +02:00
Swann Martinet
921021078b Translated using Weblate (Italian)
Currently translated at 99.2% (374 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2019-09-04 07:25:10 +02:00
J-Jamet
e5b60a8413 Small changes 2019-09-02 20:08:17 +02:00
J-Jamet
e01402f2fa Remove unused comments 2019-09-02 20:04:15 +02:00
J-Jamet
27f92e1bb5 Remove unused dependencies 2019-09-02 19:54:27 +02:00
J-Jamet
63db6de30b Links as buttons in description dialog 2019-09-02 19:42:28 +02:00
J-Jamet
7a038126cf Replace fingerprint remove icon and fix small issue 2019-09-02 19:11:51 +02:00
J-Jamet
edcfa8cf7b Better biometric key implementation 2019-09-02 18:46:55 +02:00
J-Jamet
c9594948a2 Fix small biometric issues 2019-09-02 18:30:21 +02:00
J-Jamet
66988ecb66 Better biometric view message implementation 2019-09-02 17:36:14 +02:00
J-Jamet
186ca30be8 Add preference to auto open biometric prompt 2019-09-02 14:47:03 +02:00
J-Jamet
027d581dcc Small enhancements 2019-09-02 14:04:05 +02:00
J-Jamet
adcc1c745a Fix small issues 2019-09-02 13:37:07 +02:00
J-Jamet
8682856c01 Add explanation dialog and fix store bug 2019-09-02 13:26:18 +02:00
J-Jamet
9ec976d246 Add preferences for advanced unlock 2019-09-02 12:31:56 +02:00
J Smith
9d17b49586 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (377 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-09-02 12:25:09 +02:00
Stephan Paternotte
698496d37c Translated using Weblate (Dutch)
Currently translated at 98.4% (371 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2019-09-02 12:25:01 +02:00
Allan Nordhøy
2af8c4f3c8 Translated using Weblate (English)
Currently translated at 99.7% (376 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-09-02 12:24:54 +02:00
kee
b164099b6d Translated using Weblate (German)
Currently translated at 95.0% (358 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-09-02 12:24:53 +02:00
Mesut Akcan
c19357605f Translated using Weblate (Turkish)
Currently translated at 100.0% (377 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-09-02 12:24:51 +02:00
J-Jamet
5a08fa0088 New state mode for biometric unlock 2019-09-02 12:01:36 +02:00
J-Jamet
bff87d16b1 Start biometric feature 2019-09-01 17:54:01 +02:00
J-Jamet
5b0afa447c Text size by percent 2019-09-01 17:45:39 +02:00
J-Jamet
5a882a954f Text size by percent 2019-09-01 17:44:36 +02:00
J-Jamet
47f340d576 Fix null inheritance 2019-09-01 15:49:49 +02:00
WaldiS
41df139c17 Translated using Weblate (Polish)
Currently translated at 96.6% (364 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-08-31 11:25:06 +02:00
underlineGalaxy
469c267161 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.4% (371 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-08-31 11:25:05 +02:00
Mesut Akcan
f336d4fe58 Translated using Weblate (Turkish)
Currently translated at 100.0% (377 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-08-31 11:25:05 +02:00
J-Jamet
e1d997cc91 Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2019-08-30 13:18:36 +02:00
J-Jamet
53cf4bba1b Merge branch 'feature/AndroidX' into develop 2019-08-30 10:08:40 +02:00
J-Jamet
030417dbe1 Better education methods 2019-08-30 10:07:49 +02:00
J-Jamet
59abcb115c Add .cxx to gitignore 2019-08-29 17:36:39 +02:00
J-Jamet
598dbd3794 Migrate to AndroidX and remove unused libs 2019-08-29 17:13:14 +02:00
J-Jamet
2c4a7e5576 Migrate to API 28 2019-08-29 13:58:59 +02:00
J-Jamet
02429d5790 Change lock back default value to false 2019-08-29 13:36:55 +02:00
J-Jamet
d990cb24ea Fix #314 magikeyboard notification 2019-08-29 13:35:30 +02:00
J-Jamet
e549b16dce Update CHANGELOG 2019-08-29 13:29:24 +02:00
J-Jamet
e11864a64f Remove long press in history list 2019-08-29 13:23:55 +02:00
J-Jamet
a3e74f8ee5 Fix text size in list 2019-08-29 13:15:59 +02:00
J-Jamet
36cb683404 Better file history listener implementation 2019-08-29 11:13:29 +02:00
ssantos
f7f0028033 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (377 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2019-08-27 21:24:47 +02:00
WaldiS
f06088fa12 Translated using Weblate (Polish)
Currently translated at 96.0% (362 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-08-27 21:24:29 +02:00
J-Jamet
b62ef8a2ed Update database alias 2019-08-27 13:13:31 +02:00
J-Jamet
622ba65841 Prevent information to be closed 2019-08-26 15:07:22 +02:00
J-Jamet
3a48d20d12 Change list history views 2019-08-26 14:53:23 +02:00
J-Jamet
8d6db78f55 Add file database history info as sub part of recyclerview element 2019-08-26 13:32:33 +02:00
J-Jamet
f3ba6e800a Rename OpenFileHelper 2019-08-26 10:35:18 +02:00
J-Jamet
1c4aaf9807 Encapsulate URI methods 2019-08-26 09:46:17 +02:00
J-Jamet
c65ed41efd Move error in init uri method 2019-08-26 08:03:16 +02:00
J-Jamet
1965336077 Remove WeakReference to retrieve FileDatabaseHistory 2019-08-25 12:51:32 +02:00
J-Jamet
9b7095ad4c Change App database package and name 2019-08-25 12:28:13 +02:00
J-Jamet
33767c2bf9 Add room and fully manage database file history by SQL 2019-08-25 12:04:10 +02:00
Kunzisoft
82f7e861e7 Translated using Weblate (French)
Currently translated at 100.0% (377 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-08-23 23:24:20 +02:00
J-Jamet
846b5fa449 Add octet-stream as mimetype 2019-08-22 14:08:53 +02:00
J-Jamet
baf4e676eb Updates intent-filter to recognize more .kdbx files 2019-08-22 13:59:16 +02:00
J-Jamet
a9bf3e83c4 Fix activity_password layout 2019-08-22 13:14:55 +02:00
J-Jamet
710a1b0996 Better password generation errors 2019-08-22 12:11:15 +02:00
J-Jamet
c4671b84a0 Better error management for AssignMasterKey 2019-08-22 11:53:33 +02:00
J-Jamet
c0c98d0299 Remove unused Toast and strings 2019-08-21 18:18:56 +02:00
J-Jamet
ffdec77d11 Better error and url management 2019-08-21 17:56:49 +02:00
Ldm Public
868bbe2e70 Translated using Weblate (French)
Currently translated at 96.6% (364 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-08-21 16:24:18 +02:00
jan madsen
4aac655a5d Translated using Weblate (Danish)
Currently translated at 95.5% (360 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2019-08-21 16:24:17 +02:00
Wilker Santana da Silva
510244aa70 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.1% (370 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-08-21 16:24:16 +02:00
Mesut Akcan
50840b04b4 Translated using Weblate (Turkish)
Currently translated at 96.3% (363 of 377 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-08-21 16:24:14 +02:00
J-Jamet
4d5962f5ca Merge branch 'feature/Intent_Create_File' into develop 2019-08-21 14:04:57 +02:00
J-Jamet
c9456c771c Fix fist time clipboard warning and better views implementation 2019-08-21 14:04:34 +02:00
J-Jamet
41a7a583d4 Upgrade CHANGELOG 2019-08-21 12:33:01 +02:00
J-Jamet
2e5220fa8a Better file manager installation description 2019-08-21 12:23:44 +02:00
J-Jamet
b75475d785 Fix master key creation during orientation change 2019-08-21 11:50:06 +02:00
J-Jamet
362ef06bb5 Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2019-08-20 21:41:44 +02:00
J-Jamet
4a80c9f9f9 Remove unused permission dispatcher 2019-08-20 14:07:10 +02:00
J-Jamet
f1fdb9fc84 Remove SAF setting and fix education when create button is gone 2019-08-20 13:55:57 +02:00
J-Jamet
5bb9168c29 Simplify database file creation 2019-08-20 13:12:44 +02:00
J-Jamet
0245dcd8e8 Merge branch 'develop' into feature/Intent_Create_File 2019-08-20 10:33:47 +02:00
J-Jamet
b31bfa1d4f Upgrade version to 2.5.0.0beta23 2019-08-20 10:33:02 +02:00
J-Jamet
0778f22b68 Create database with additional button 2019-08-19 20:01:05 +02:00
J-Jamet
4808696398 Remove unused create file verification 2019-08-19 19:28:08 +02:00
J-Jamet
0ea7b5b25f Merge branch 'develop' into feature/Intent_Create_File 2019-08-19 19:10:13 +02:00
Hosted Weblate
995785de9f Merge branch 'origin/master' into Weblate. 2019-08-19 18:01:01 +02:00
Marco Scaglioni
5deef427c0 Translated using Weblate (Italian)
Currently translated at 100.0% (356 of 356 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2019-08-19 18:01:01 +02:00
J-Jamet
024c6631d8 Merge tag '2.5.0.0beta22' into develop
2.5.0.0beta22
2019-08-19 17:58:00 +02:00
J-Jamet
78354e4736 Merge branch 'release/2.5.0.0beta22' 2019-08-19 17:57:42 +02:00
J-Jamet
b9a792e6bd Upgrade default fastfile 2019-08-19 17:48:56 +02:00
J-Jamet
c533d21250 Fix small view warning 2019-08-19 17:30:56 +02:00
J-Jamet
74572c8102 Upgrade version and CHANGELOGS 2019-08-19 17:13:28 +02:00
J-Jamet
b8de64fab0 New icon background 2019-08-19 17:06:38 +02:00
J-Jamet
877b909205 Merge branch 'translations' into develop 2019-08-19 16:43:51 +02:00
J-Jamet
1b1dcc0f45 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2019-08-19 16:43:23 +02:00
J-Jamet
94e5988794 Fix remove entry for databaseV1 2019-08-19 15:17:00 +02:00
J-Jamet
b22333fda5 Show empty title in entry content view if title is empty 2019-08-19 15:00:33 +02:00
J-Jamet
400c6bef78 Disable recycle bin at bottom option if natural order 2019-08-19 14:44:56 +02:00
J-Jamet
edf6c2ff07 Fix remove action for natural order 2019-08-19 14:16:29 +02:00
J-Jamet
5eec1a276c Fix recyclebin update 2019-08-19 01:42:23 +02:00
J-Jamet
f707fd7649 Fix move and copy entry in database V1 root 2019-08-19 01:03:09 +02:00
J-Jamet
75b028daf3 Fix actions duplicate entries 2019-08-19 00:07:14 +02:00
J-Jamet
c6f259d18f Fix move action 2019-08-18 22:57:44 +02:00
J-Jamet
954d522341 Add UUID in Entry View 2019-08-18 22:32:33 +02:00
J-Jamet
1f8d17d27e Merge tag '2.5.0.0beta21' into develop
2.5.0.0beta21
2019-08-18 19:18:36 +02:00
J-Jamet
c2781af38d Merge branch 'release/2.5.0.0beta21' 2019-08-18 19:18:27 +02:00
J-Jamet
a1cd2683d4 Fix small views issues 2019-08-18 18:31:14 +02:00
J-Jamet
6d671f53c2 Fix content_description and translation 2019-08-18 17:48:21 +02:00
J-Jamet
4efb81f597 Fix translations 2019-08-18 17:11:19 +02:00
J-Jamet
faddeb6e2d Fix for entry duplicate and import 2019-08-18 16:47:02 +02:00
J-Jamet
e36629968d Fix edit group #296 2019-08-18 15:07:32 +02:00
J-Jamet
0b756797f6 Fix entry number size 2019-08-18 14:55:22 +02:00
J-Jamet
110c7bbbc7 Upgrade CHANGELOG 2019-08-18 14:46:00 +02:00
J-Jamet
639c6dc4ac Add number of entries in node view 2019-08-18 14:43:05 +02:00
J-Jamet
f605d36adf Remove TODO sort 2019-08-18 14:17:04 +02:00
J-Jamet
f6d6c134f4 Fix colorTextInverse 2019-08-18 14:12:31 +02:00
J-Jamet
a0c07654df Update CHANGELOGS 2019-08-18 11:14:07 +02:00
J-Jamet
03ab688abe Fix bug open button disabled with only keyFile #295 2019-08-18 11:10:07 +02:00
J-Jamet
4a28802b02 Add RecycleBin at Bottom sort option 2019-08-17 17:11:06 +02:00
J-Jamet
18f8fe7cc3 Upgrade CHANGELOG 2019-08-17 16:36:45 +02:00
J-Jamet
459606f5d5 Fix sort for natural database 2019-08-17 16:34:56 +02:00
J-Jamet
3d53c31680 Better sort implementation 2019-08-17 15:15:21 +02:00
J-Jamet
173dd5b59b Add natural database sort 2019-08-17 14:29:18 +02:00
J-Jamet
8536de3555 Remove email (no more code) 2019-08-17 13:21:48 +02:00
J-Jamet
722ba5c34d Move construct tree methods in Importer 2019-08-17 13:20:53 +02:00
J-Jamet
3ccfd7226b Upgrade to version 2.5.0.0beta21 and fix nested groups in v1 #292 2019-08-17 12:58:11 +02:00
J-Jamet
578da7bae5 Fix copy_field error 2019-08-16 21:01:06 +02:00
J-Jamet
6916389657 Merge tag '2.5.0.0beta20' into develop
2.5.0.0beta20
2019-08-16 20:34:23 +02:00
J-Jamet
da4f41732a Merge branch 'release/2.5.0.0beta20' 2019-08-16 20:34:13 +02:00
J-Jamet
0414adf5de Add changelog 2019-08-16 19:17:59 +02:00
J-Jamet
d9b2942f66 Fix Entry information is not correct #286 2019-08-16 19:08:59 +02:00
J-Jamet
cbe7907b07 Upgrade version to 2.5.0.0beta20 2019-08-16 18:41:24 +02:00
J-Jamet
b737501d4d Start change database file creation 2019-08-16 11:24:00 +02:00
Allan Nordhøy
c303ffafb5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.3% (343 of 356 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-08-16 01:23:47 +02:00
Kunzisoft
6f513b4920 Translated using Weblate (French)
Currently translated at 99.4% (354 of 356 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-08-16 01:23:46 +02:00
Wilker Santana da Silva
a83c60583f Translated using Weblate (English)
Currently translated at 99.7% (355 of 356 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-08-16 01:23:45 +02:00
Kunzisoft
cde8950257 Translated using Weblate (English)
Currently translated at 99.7% (355 of 356 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-08-16 01:23:45 +02:00
jan madsen
0b4dd1e909 Translated using Weblate (Danish)
Currently translated at 96.6% (344 of 356 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2019-08-16 01:23:45 +02:00
Wilker Santana da Silva
28e2600271 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.4% (354 of 356 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-08-16 01:23:44 +02:00
J-Jamet
53cc4f74c8 Add logo for Google Play Store guidelines 2019-08-15 13:28:02 +02:00
J-Jamet
3989ca3ad1 Merge tag '2.5.0.0beta19' into develop
2.5.0.0beta19
2019-08-14 19:26:04 +02:00
J-Jamet
977782a9f7 Merge branch 'release/2.5.0.0beta19' 2019-08-14 19:25:54 +02:00
J-Jamet
f100dda20b Fix icon resolution 2019-08-14 18:34:01 +02:00
J-Jamet
9c0a140a17 Change small code 2019-08-14 14:35:26 +02:00
J-Jamet
b4bcaf54ad Change small code 2019-08-14 14:32:57 +02:00
J-Jamet
82c0ca0f3c Kotlinized KDF 2019-08-14 14:08:03 +02:00
J-Jamet
bfbd81e3ee Better key retrieve code 2019-08-14 14:00:08 +02:00
J-Jamet
67fecf3fef Remove sound and vibration for notification 2019-08-14 10:41:12 +02:00
J-Jamet
c65d96802e Add clipboard clear at closing dependency 2019-08-14 09:23:29 +02:00
J-Jamet
64af8ddc2e Remove Request Focus 2019-08-13 19:49:14 +02:00
J-Jamet
732f472146 Fix warnings 2019-08-13 19:33:57 +02:00
J-Jamet
6dc46604a4 Add execution to gradlew 2019-08-13 19:18:46 +02:00
J-Jamet
f0c3071de1 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translation 2019-08-13 19:17:43 +02:00
J-Jamet
b250ad2f8b Upgrade CHANGELOG 2019-08-13 17:41:46 +02:00
J-Jamet
69390a81ab Fix lock with back button 2019-08-13 17:22:57 +02:00
J-Jamet
c398b92eb1 Encapsulate DatabaseTaskNotification 2019-08-13 17:12:35 +02:00
J-Jamet
3018206e2f New icons for notifications 2019-08-13 16:54:36 +02:00
J-Jamet
811d0f2534 Change default value for timeout 2019-08-13 16:17:10 +02:00
J-Jamet
2b33d785ac Add clear database option when close clipboard notification 2019-08-13 16:06:30 +02:00
J-Jamet
c7a8322c5d Change inputText importance for accessibility and autofill 2019-08-13 15:26:46 +02:00
J-Jamet
37e722847a Fix color style and remove colorAccentCompat 2019-08-13 14:19:13 +02:00
J-Jamet
4aa8d892a4 Fix small button color in API 21 2019-08-13 13:41:05 +02:00
J-Jamet
e164062cf3 Remove menu in selection mode 2019-08-13 13:27:09 +02:00
J-Jamet
935d3e1c4b Better notification implementation 2019-08-13 12:36:58 +02:00
J-Jamet
17eadcee2b Better notification implementation 2019-08-13 11:33:42 +02:00
J-Jamet
5ffbe0e9ee New Magikeyboard notification flow #182 2019-08-12 19:37:34 +02:00
J-Jamet
4c3da45141 Fix lock with magikeyboard 2019-08-12 15:28:07 +02:00
J-Jamet
c516ef7c28 Fix remove entry in Magikeyboard when lock in database performed 2019-08-12 14:55:51 +02:00
J-Jamet
dcf654cf0a Add entry title in Magikeyboard 2019-08-12 14:05:23 +02:00
J-Jamet
23f2e5decc Better Magikeyboard buttons width 2019-08-12 13:23:45 +02:00
J-Jamet
ddbf03bc91 Better button style 2019-08-12 12:33:35 +02:00
J-Jamet
94070ea5e0 Fix destroy Magikeyboard service when lock activity 2019-08-12 11:24:21 +02:00
J-Jamet
725d39626f Fix clear data when opening database #285 2019-08-12 11:01:38 +02:00
Zhai2333
25655abbbb Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-08-11 16:23:55 +02:00
Zhai2333
60bee79bc5 Translated using Weblate (Chinese (Traditional))
Currently translated at 23.8% (82 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2019-08-11 16:23:55 +02:00
C. Rüdinger
8f9d278d2f Translated using Weblate (German)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-08-11 16:23:54 +02:00
CurlingTongs
dc120135b1 Translated using Weblate (German)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-08-11 16:23:53 +02:00
J-Jamet
d2f56e7472 Retrieve database with a singleton 2019-08-09 23:29:28 +02:00
J-Jamet
3877e1aa22 Update description 2019-08-09 16:27:12 +02:00
J-Jamet
e08e5eca3a Fix entries search 2019-08-09 16:18:56 +02:00
J-Jamet
7321cc067f Fix app:theme deprecation 2019-08-07 14:00:56 +02:00
J-Jamet
43f14e1474 Fix style for pre lollipop devices 2019-08-07 13:50:42 +02:00
J-Jamet
ced6a77819 Add handler to fix password freeze #226 2019-08-07 11:27:11 +02:00
J-Jamet
bca777a97e Rename FingerPrintViewsManager 2019-08-07 11:05:16 +02:00
J-Jamet
5b98129da9 Fix fingerprint 2019-08-07 11:00:13 +02:00
J-Jamet
dc37ab74c1 Fix fingerprint 2019-08-07 10:57:35 +02:00
J-Jamet
ee76a35728 Fix fingerprint 2019-08-07 10:56:04 +02:00
J-Jamet
e31ea8c916 Better fingerprint version code 2019-08-06 20:22:49 +02:00
J-Jamet
41561cb7b6 Add comment fingerprint 2019-08-06 19:41:08 +02:00
J-Jamet
0c8fdca6f3 Fix assist structure for old device 2019-08-06 19:23:20 +02:00
J-Jamet
a50626dafb Encapsulate Fingerprint methods 2019-08-06 19:07:44 +02:00
J-Jamet
ca55081437 Better focus and revert hide credential view 2019-08-06 14:11:52 +02:00
J-Jamet
5cd66d074e Fix layout scroll and landscape for tablet 2019-08-06 12:55:52 +02:00
J-Jamet
9529e3a7ba Better entry top view 2019-08-06 12:14:29 +02:00
J-Jamet
d3e2668d85 Add Hindi source file 2019-08-06 11:54:18 +02:00
J-Jamet
b3b2bb90e1 Setting to delete the password entered after a connection attempt #214 2019-08-06 11:49:06 +02:00
J-Jamet
9c597665bf Hide credentials when opening database #250 2019-08-06 11:00:35 +02:00
J-Jamet
a845436af4 Fix small visual bug 2019-08-05 18:23:13 +02:00
J-Jamet
cabd487fa5 Adjust resize to save form 2019-08-05 18:18:55 +02:00
J-Jamet
9c7c43e7da Move change master key menu in Settings 2019-08-05 18:03:58 +02:00
J-Jamet
0c421c4906 Close button in edit activity 2019-08-05 17:27:03 +02:00
J-Jamet
4df8880b0d Fix classic icon view 2019-08-05 17:21:30 +02:00
J-Jamet
45046ee01a Fix icon cache density issue 2019-08-05 16:43:32 +02:00
J-Jamet
2ff6522b60 Fix visual bug 2019-08-05 16:05:53 +02:00
J-Jamet
ee8c589ea3 Fix extra card view 2019-08-05 13:30:14 +02:00
J-Jamet
b31d40dbb6 Better entry views 2019-08-05 13:24:04 +02:00
J-Jamet
970b966bcb Collapsing Toolbar for entry activity 2019-08-05 12:25:48 +02:00
J-Jamet
6f1dc14bdc Rename layout for better visibility 2019-08-05 09:49:58 +02:00
J-Jamet
949e6f247d Change copying notification priority #281 and fix never timeout 2019-08-03 14:17:58 +02:00
J-Jamet
77a848cf0e Fira mono font #223 2019-08-03 12:00:49 +02:00
J-Jamet
21e268b8c2 Merge branch 'feature/Refactor_Kotlin' into develop 2019-08-02 20:55:47 +02:00
J-Jamet
580c761fa0 New icon selection style and fix views 2019-08-02 20:51:17 +02:00
Ldm Public
e13b12550c Translated using Weblate (French)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-08-02 19:12:28 +02:00
J-Jamet
05dc421ea8 Fix orientation change entry with many bugs #228 2019-08-02 14:59:59 +02:00
Allan Nordhøy
423957d023 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-08-01 07:14:11 +02:00
bluepencil
c3b584973b Translated using Weblate (Korean)
Currently translated at 43.8% (151 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2019-08-01 07:14:10 +02:00
Marco Scaglioni
74e1805970 Translated using Weblate (Italian)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2019-08-01 07:14:02 +02:00
Wilker Santana da Silva
e2743e2c61 Translated using Weblate (English)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-08-01 07:14:02 +02:00
mondstern
59f0b90c72 Translated using Weblate (German)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-08-01 07:14:01 +02:00
Wilker Santana da Silva
c962096fd5 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-08-01 07:14:01 +02:00
Mesut Akcan
d9aa9f6cb3 Translated using Weblate (Turkish)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-08-01 07:13:53 +02:00
J-Jamet
874e422dbc Add vertical padding to autofill label #262 2019-07-31 15:31:39 +02:00
J-Jamet
63c825c056 Fix modify entry with no parent 2019-07-31 15:20:42 +02:00
J-Jamet
b507fa2a09 Fix long press magikeyboard 2019-07-31 14:54:20 +02:00
J-Jamet
eb2040edbd Fix ref autofill #274 2019-07-31 14:49:16 +02:00
J-Jamet
02ba1dabe7 Fix ref keyboard form field #274 2019-07-31 14:34:18 +02:00
J-Jamet
03494d78a5 Fix dialog preference description 2019-07-31 13:49:30 +02:00
J-Jamet
b598945903 Fix orientation change dimension 2019-07-31 12:19:01 +02:00
J-Jamet
8bf017e14e Allow no password default false 2019-07-30 09:51:57 +02:00
J-Jamet
00e4d77503 Fix crash during swipe keyboard notification #258 2019-07-29 21:55:05 +02:00
J-Jamet
e6efdadb6f Fix Autofill layout background #238 2019-07-29 21:41:18 +02:00
J-Jamet
02a8e7ea75 Fix Autofill setting 2019-07-29 21:02:43 +02:00
J-Jamet
dc9b7591a0 Update CHANGELOG 2019-07-29 20:26:53 +02:00
J-Jamet
7ae0d329e1 Fix bad base64 decoder implementation #222 #140 2019-07-29 20:23:52 +02:00
J-Jamet
bff9ec86ff Change layout to better visibility in landscape mode 2019-07-29 17:35:56 +02:00
J-Jamet
f445fbca3d Merge branch 'feature/Refactor_Kotlin' of github.com:Kunzisoft/KeePassDX into feature/Refactor_Kotlin 2019-07-27 09:35:42 +02:00
J-Jamet
9079ce30a0 Fix preference 2019-07-26 18:21:54 +02:00
J-Jamet
2202f718b9 Add database task notification 2019-07-26 17:08:05 +02:00
J-Jamet
63b80634c2 Fix lock timeout 2019-07-26 11:22:23 +02:00
J-Jamet
9bbfeaba72 Better database creation runnable 2019-07-25 15:23:04 +02:00
J-Jamet
2d640dbe62 Fix database creation index 2019-07-25 14:50:51 +02:00
J-Jamet
2ce3fc3bd6 Fix first Recycle Bin creation 2019-07-24 21:23:43 +02:00
J-Jamet
009710f854 Upgrade CHANGELOG 2019-07-24 16:53:21 +02:00
J-Jamet
7eeff1b50a Add default Recycle Bin name as string resource 2019-07-24 16:49:18 +02:00
J-Jamet
6f736d6415 Better index action implementation 2019-07-24 16:37:24 +02:00
J-Jamet
3dc988dd08 Fix recycleBin 2019-07-24 15:55:52 +02:00
Deleted User
ca292fbb85 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-07-23 19:31:06 +02:00
J-Jamet
53a3fe83cb Fix small code 2019-07-22 13:57:32 +02:00
J-Jamet
d667940157 Fix password encoding 2019-07-22 13:54:58 +02:00
J-Jamet
404fb9b39f Change licence in XML and .kt files 2019-07-22 11:53:12 +02:00
J-Jamet
d1c4bdb8eb Kotlinized V4 XML class 2019-07-22 11:49:49 +02:00
J-Jamet
7b67e4e811 Fix search 2019-07-22 11:26:47 +02:00
J-Jamet
2b387c8613 Fix list rebuild and add button animation 2019-07-20 01:25:19 +02:00
J-Jamet
c7f6cb6747 Fix remove crash 2019-07-20 01:20:19 +02:00
J-Jamet
5e5dc58482 Fix warning 2019-07-20 00:18:13 +02:00
J-Jamet
80d0510b86 Refactor util 2019-07-20 00:03:03 +02:00
J-Jamet
fcd3f7c3fe Refactorize uri util 2019-07-19 23:36:36 +02:00
J-Jamet
38abb9ca49 Refactorize string util 2019-07-19 21:57:50 +02:00
J-Jamet
c47834f470 Fix small crypto code 2019-07-19 19:15:33 +02:00
J-Jamet
b899212d14 Fix small crypto code 2019-07-19 19:06:34 +02:00
J-Jamet
5a11e47653 Fix small crypto code 2019-07-19 18:46:11 +02:00
J-Jamet
d0faf0f1b6 Fix small dialog code 2019-07-19 18:41:53 +02:00
J-Jamet
27de6b456b Kotlinized Stylish 2019-07-19 17:59:29 +02:00
J-Jamet
92f6684ca9 Fix education add button 2019-07-19 16:10:45 +02:00
J-Jamet
59c45f5627 Kotlinized icons 2019-07-19 16:03:40 +02:00
Ldm Public
35c2075f73 Translated using Weblate (French)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-07-18 23:05:52 +02:00
J-Jamet
4b659248f7 Kotlinized Magikeyboard 2019-07-18 21:05:29 +02:00
J-Jamet
3d01e09198 Kotlinized Notification 2019-07-18 19:44:13 +02:00
J-Jamet
a7805d9d4c Kotlinized Password Generator 2019-07-18 18:40:01 +02:00
J-Jamet
2439c0e7cd Kotlinized Preference 2019-07-18 18:19:03 +02:00
J-Jamet
67f99a4430 Kotlinized Fingerprint 2019-07-18 18:00:59 +02:00
J-Jamet
0a8c7700dc Kotlinized views 2019-07-18 16:55:02 +02:00
Mattéo Rossillol‑‑Laruelle
68ca17a0da Translated using Weblate (French)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-07-17 22:28:41 +02:00
J-Jamet
dcc7c4d39e Kotlinized preference 2019-07-17 22:20:07 +02:00
J-Jamet
06fcbf8995 Kotlinized preference dialog fragment and fix result action runnable 2019-07-17 20:46:22 +02:00
J-Jamet
a939d06f77 Kotlinized crypto engine 2019-07-17 16:19:24 +02:00
bluepencil
11e342ae19 Translated using Weblate (Korean)
Currently translated at 0.9% (3 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2019-07-16 18:25:04 +02:00
J-Jamet
5ac40d13a5 Change Readme.md year 2019-07-16 16:36:21 +02:00
J-Jamet
1dea366cec Fix translation 2019-07-16 15:49:15 +02:00
J-Jamet
7e5ae5a8af Fix small code 2019-07-16 12:22:49 +02:00
bluepencil
4e430b2fd4 Added translation using Weblate (Korean) 2019-07-16 04:20:19 +02:00
Júlio Costa
d61c345630 Translated using Weblate (Portuguese (Brazil))
Currently translated at 45.2% (156 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-07-15 20:02:57 +02:00
J-Jamet
0c7aa3fad2 Merge branch 'feature/Refactor_Kotlin' of github.com:Kunzisoft/KeePassDX into feature/Refactor_Kotlin 2019-07-14 17:03:44 +02:00
J-Jamet
8de7e4a326 Upgrade CHANGELOG 2019-07-14 14:02:53 +02:00
J-Jamet
4fd5d72d97 Better ProgressDialog implementation 2019-07-13 19:51:52 +02:00
J-Jamet
d08d2552aa Update Gradle 2019-07-13 16:38:47 +02:00
J-Jamet
6b6968cbbe Update Gradle 2019-07-13 15:38:47 +02:00
Thomas johansen
49157a38ae Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-07-12 21:01:58 +02:00
J-Jamet
402a0d3e43 Kotlinized Fragments 2019-07-11 15:45:05 +02:00
J-Jamet
5cd8cbd514 Fix PwGroupV3 and PwEntryV3 2019-07-11 14:51:20 +02:00
J-Jamet
32537e85a2 Move classes 2019-07-11 13:54:18 +02:00
J-Jamet
d937ca85a2 Kotlinized fragments 2019-07-10 23:37:40 +02:00
J-Jamet
6a5ed7f460 Fix kotlin null crash 2019-07-10 19:10:59 +02:00
J-Jamet
a158e96ba6 Refactor BasicViewHolder 2019-07-10 17:59:59 +02:00
J-Jamet
7ad43dbed7 Kotlinized Activities 2019-07-10 17:47:06 +02:00
J-Jamet
63f261f169 Fix add new entry 2019-07-09 17:12:00 +02:00
J-Jamet
f36849e815 Kotlinized Autofill and BackupAgent 2019-07-07 21:39:27 +02:00
J-Jamet
b0bc0f9d85 Kotlinized Adapters and fix StyledAttributes 2019-07-07 20:53:08 +02:00
J-Jamet
cd271e6f04 Kotlinized ListNodesFragment 2019-07-07 20:22:06 +02:00
J-Jamet
d6bac74e1b Kotlinized GroupActivity 2019-07-07 20:01:17 +02:00
J-Jamet
eda7fa0fe7 Fix NodeHandler parameter 2019-07-07 18:49:37 +02:00
J-Jamet
191e0cc654 Kotlinized EntryEditActivity 2019-07-07 18:48:56 +02:00
J-Jamet
cc35a1e8aa Fix deprecated buttons 2019-07-07 18:47:32 +02:00
J-Jamet
7dfe85450d Kotlinized EntryActivity 2019-07-07 16:37:57 +02:00
J-Jamet
e8b341c69f Refactor Education 2019-07-07 15:23:47 +02:00
J-Jamet
77c397e0d1 Fix small bugs 2019-07-04 19:10:33 +02:00
J-Jamet
15794f09c3 Move database history in singleton 2019-07-04 19:04:42 +02:00
J-Jamet
cdf8baeb10 Kotlinized PwDate 2019-07-04 15:25:47 +02:00
J-Jamet
e4b550afc1 4 spaces files harmonisation #184 2019-07-04 14:34:06 +02:00
WaldiS
49bb34b742 Translated using Weblate (Polish)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-07-04 12:02:05 +02:00
C. Rüdinger
ab01d01fce Translated using Weblate (German)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-06-29 20:02:47 +02:00
Allan Nordhøy
4905c262ba Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-06-28 15:02:12 +02:00
Allan Nordhøy
ce05bb467e Translated using Weblate (English)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-06-28 15:02:12 +02:00
CurlingTongs
8f0fe89579 Translated using Weblate (German)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-06-28 15:02:11 +02:00
Noel
23bd6e20a4 Translated using Weblate (Spanish)
Currently translated at 49.0% (169 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2019-06-24 17:01:43 +02:00
Yaron Shahrabani
3f0071ac58 Translated using Weblate (Hebrew)
Currently translated at 21.2% (73 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/he/
2019-06-18 21:05:56 +02:00
GiulioEl
776660923b Translated using Weblate (Italian)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2019-06-15 09:40:31 +02:00
J-Jamet
32f170f644 Remove unused import 2019-06-10 14:40:08 +02:00
J-Jamet
93222990b5 Kotlinized database package 2019-06-02 16:10:57 +02:00
J-Jamet
8890296beb Kotlinized importers 2019-06-02 14:04:36 +02:00
J-Jamet
9907af8135 Kotlinized many classes 2019-06-02 13:36:05 +02:00
J-Jamet
17ec357c1b Kotlinized PwNode PwEntry PwGroup 2019-06-02 02:41:51 +02:00
J-Jamet
575e967627 Kotlinized PwNode 2019-06-01 15:01:02 +02:00
J-Jamet
0b53e84761 Kotlinized small code 2019-06-01 13:58:41 +02:00
J-Jamet
fadf78aabd Remove PwVersion 2019-06-01 13:51:03 +02:00
J-Jamet
47fce14d16 Kotlinized PwIcon 2019-06-01 13:47:44 +02:00
J-Jamet
149b6fbcd4 Kotlinized PwNodeId 2019-06-01 13:12:51 +02:00
J-Jamet
e649be230e Remove clone 2019-06-01 12:58:28 +02:00
J-Jamet
6ba45c3d87 Better search implementation, kotlinize, start remove clone 2019-06-01 12:34:06 +02:00
J-Jamet
0e701189a3 Fix search 2019-06-01 11:10:42 +02:00
J-Jamet
af316607e1 Fix copy and clone 2019-05-30 21:11:33 +02:00
Allan Nordhøy
548039c66f Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (342 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-05-16 17:49:10 +02:00
J-Jamet
e75a2502d1 Better index implementation 2019-05-10 00:39:31 +02:00
J-Jamet
6be2148951 Fix opening bugs 2019-05-09 23:41:26 +02:00
ButterflyOfFire
f149688da6 Translated using Weblate (Arabic)
Currently translated at 71.0% (245 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-05-07 12:49:02 +02:00
J-Jamet
8f77e5874e Refactor database import 2019-05-03 18:33:23 +02:00
J-Jamet
eabd2d3e4c Refactor 2019-05-03 17:15:53 +02:00
J-Jamet
e460a4fe4f Refactor database import 2019-04-24 21:40:28 +02:00
J-Jamet
9e79da0efc Refactor database elements 2019-04-24 20:38:13 +02:00
Tobias Johansson
6851d4a1d3 Translated using Weblate (Swedish)
Currently translated at 21.2% (73 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2019-04-18 15:05:18 +02:00
J-Jamet
5705d367ed Fix UI thread for ProgressDialogFragment 2019-04-13 18:03:31 +02:00
J-Jamet
d1f88112ce Finish database encapsulation 2019-04-12 22:22:17 +02:00
J-Jamet
eba1dbc8fa Move touch, fix tree construction, refactor getPwDatabase 2019-04-12 21:46:40 +02:00
J-Jamet
dcb819b087 Fix display message Toast bug 2019-04-12 19:19:13 +02:00
J-Jamet
25ced826c4 Refactor tree perform (to debug) 2019-04-12 17:56:07 +02:00
Noel
5a30c3219e Translated using Weblate (Spanish)
Currently translated at 48.4% (167 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2019-04-04 22:04:43 +02:00
Noel
6a5ac3729d Translated using Weblate (German)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-04-04 22:04:39 +02:00
Ldm Public
31326224ac Translated using Weblate (French)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-04-04 22:04:38 +02:00
Semen Turchikhin
811a44574a Translated using Weblate (Russian)
Currently translated at 64.1% (221 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-03-26 05:34:41 +01:00
aasami
6782442d8e Translated using Weblate (Slovak)
Currently translated at 25.2% (87 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sk/
2019-03-25 15:38:42 +01:00
J-Jamet
a376d2f286 Refactor Entry and Group 2019-03-21 17:58:08 +01:00
Rui Mendes
ce3e1c86bc Translated using Weblate (Portuguese (Portugal))
Currently translated at 45.5% (157 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2019-03-21 15:03:56 +01:00
J-Jamet
20f7675135 Refactor database elements (In PROGRESS) 2019-03-15 16:48:59 +01:00
J-Jamet
dad3edee5e Refactor database elements 2019-03-15 10:58:04 +01:00
J-Jamet
1bfdae53c6 Add a warning message in save progress dialog 2019-03-14 18:16:49 +01:00
J-Jamet
e5902b9cf6 First refactoring pass 2019-03-14 18:00:09 +01:00
George
88829ca91a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-03-12 13:03:54 +01:00
abidin toumi
4bdf99b2f4 Translated using Weblate (Arabic)
Currently translated at 66.1% (228 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-03-12 13:03:51 +01:00
George
54883bb043 Translated using Weblate (Chinese (Simplified))
Currently translated at 84.6% (292 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-03-10 15:03:36 +01:00
abidin toumi
6db55b5eaa Translated using Weblate (Arabic)
Currently translated at 49.9% (172 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2019-03-09 12:03:40 +01:00
Ldm Public
821dc6df63 Translated using Weblate (French)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-03-07 17:03:35 +01:00
George
8de86e26d6 Translated using Weblate (Chinese (Simplified))
Currently translated at 83.8% (289 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-03-07 17:03:35 +01:00
WaldiS
1cf8b1fc7d Translated using Weblate (Polish)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-03-04 15:58:02 +01:00
Nils B
f469fa224e Translated using Weblate (German)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-03-04 15:58:01 +01:00
George
56912ddaf7 Translated using Weblate (Chinese (Simplified))
Currently translated at 71.3% (246 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-03-04 15:58:00 +01:00
J-Jamet
620b1b4f02 Refactor database runnable and prevent activity to be closed during saving 2019-02-28 16:34:25 +01:00
J-Jamet
2208c67b62 Fix renaming groups #194 2019-02-28 10:46:35 +01:00
abvgeej
6f6b53ff73 Translated using Weblate (Russian)
Currently translated at 64.1% (221 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-02-26 11:18:17 +01:00
jan madsen
529197fb7d Translated using Weblate (Danish)
Currently translated at 98.6% (340 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2019-02-26 11:18:14 +01:00
J-Jamet
953e63f55d Add selection mode and fix read only 2019-02-23 14:39:03 +01:00
Mesut Akcan
783cbc634e Translated using Weblate (Turkish)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-02-23 08:18:07 +01:00
Casiyre
9ac25b0d74 Translated using Weblate (English)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-02-23 08:18:06 +01:00
J-Jamet
5f051ed2a9 Merge branch 'feature/50-Back_Lock' into develop 2019-02-22 17:39:50 +01:00
J-Jamet
b40d2f3b9a Fix keyboard settings 2019-02-22 17:29:17 +01:00
J-Jamet
3f90276ce0 Refactor Timeout 2019-02-22 16:52:02 +01:00
J-Jamet
66e077f8f1 Fix keyboard entry disappear 2019-02-22 16:47:24 +01:00
J-Jamet
8426b1d91f Fix opening activity 2019-02-22 16:13:40 +01:00
J-Jamet
3b55937085 Fix keyboard entry selection 2019-02-22 15:10:07 +01:00
J-Jamet
da86a971b5 Refactor Autofill and Keyboard selection 2019-02-22 13:34:07 +01:00
J-Jamet
ff7f0e0a60 Add option for back lock 2019-02-21 16:25:00 +01:00
J-Jamet
9233b610c2 Refactor keys of preferences 2019-02-21 15:52:00 +01:00
J-Jamet
9dc183b9b7 Update version and Changelog 2019-02-21 14:21:22 +01:00
J-Jamet
d000b6f884 Add lock button always visible 2019-02-21 14:17:26 +01:00
J-Jamet
e0b0dd134f Refactor launch method 2019-02-21 13:50:07 +01:00
J-Jamet
e3d09bff36 Merge branch 'feature/WorkFlow_Connection' into develop 2019-02-21 13:40:26 +01:00
naofum
5e901812ad Translated using Weblate (Japanese)
Currently translated at 32.8% (113 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2019-02-17 10:22:32 +01:00
Balázs Meskó
b7f9ff8c98 Translated using Weblate (Hungarian)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2019-02-12 16:10:26 +01:00
Balázs Meskó
76ed603bf1 Translated using Weblate (Hungarian)
Currently translated at 93.3% (322 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2019-02-11 14:30:17 +01:00
Balázs Meskó
337816b7f2 Translated using Weblate (Hungarian)
Currently translated at 92.2% (318 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2019-02-09 06:09:05 +01:00
Balázs Meskó
37b1566535 Translated using Weblate (Hungarian)
Currently translated at 39.1% (135 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2019-02-06 20:55:59 +01:00
J-Jamet
9864d1e62d Fix setting opening 2019-01-24 18:21:44 +01:00
naofum
e3a07377e3 Translated using Weblate (Japanese)
Currently translated at 31.3% (108 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2019-01-18 08:20:49 +01:00
Hadrián Candela
f54b908dbb Translated using Weblate (Galician)
Currently translated at 8.7% (30 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2019-01-12 11:06:53 +01:00
Allan Nordhøy
e3f3a5faf7 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.6% (340 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-01-09 04:06:57 +01:00
Osoitz
2046e15f65 Translated using Weblate (Basque)
Currently translated at 21.4% (74 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2018-12-31 18:09:25 +01:00
Ldm Public
4e8f554ae6 Translated using Weblate (French)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-12-29 00:09:26 +01:00
Éfrit
0f21040888 Translated using Weblate (French)
Currently translated at 99.7% (344 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-12-27 22:32:55 +01:00
J-Jamet
0d86fa0d95 Fix infinite time with keyboard and autofill 2018-12-19 22:38:43 +01:00
J-Jamet
0ad8826b51 New lock implementation 2018-12-19 22:27:06 +01:00
J-Jamet
a535f123ff Fix settings timeout 2018-12-19 19:04:44 +01:00
J-Jamet
4fcb2de2bb Rename lock or reset timeout method 2018-12-19 17:20:37 +01:00
random r
8885192db1 Translated using Weblate (Italian)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-12-19 16:09:17 +01:00
J-Jamet
7ebf64939b Merge branch 'feature/Unexpected_Lock' into develop 2018-12-19 12:07:28 +01:00
J-Jamet
6712b0927d Refactor entry views and check focus to reset timeout 2018-12-19 11:58:39 +01:00
J-Jamet
8853bb7618 Move LockingActivity 2018-12-17 19:07:24 +01:00
J-Jamet
cbc8ec9880 Refactor Lock with Kotlin 2018-12-17 19:00:08 +01:00
J-Jamet
6372099cb2 Add Kotlin dependency 2018-12-17 16:04:35 +01:00
Mesut Akcan
15fa114742 Translated using Weblate (Turkish)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-12-11 11:08:59 +01:00
wellinkstein
c16ded1377 Translated using Weblate (French)
Currently translated at 98.6% (340 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-12-11 05:42:13 +01:00
Mesut Akcan
8ecd0e0eb1 Translated using Weblate (Turkish)
Currently translated at 69.3% (239 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-12-08 15:08:40 +01:00
Mesut Akcan
c5895ed61b Translated using Weblate (Turkish)
Currently translated at 69.0% (238 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-12-07 14:08:36 +01:00
ssantos
35751844b0 Translated using Weblate (German)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-12-07 14:08:33 +01:00
Mesut Akcan
fd66e9acb8 Translated using Weblate (Turkish)
Currently translated at 56.8% (196 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-12-04 10:11:57 +01:00
Mesut Akcan
310ae58388 Translated using Weblate (Turkish)
Currently translated at 51.9% (179 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-12-02 12:08:30 +01:00
Bruno Guerreiro
d4a31567cd Translated using Weblate (Portuguese (Portugal))
Currently translated at 36.8% (127 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2018-11-30 20:45:31 +01:00
Mesut Akcan
d54abf66fe Translated using Weblate (Turkish)
Currently translated at 48.7% (168 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-11-30 11:08:25 +01:00
MESUT AKCAN
792b3b7a81 Translated using Weblate (Turkish)
Currently translated at 33.0% (114 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-11-24 11:09:17 +01:00
mohammadA
0345606903 Translated using Weblate (Arabic)
Currently translated at 35.1% (121 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2018-11-24 11:09:13 +01:00
MESUT AKCAN
06aec23c8a Translated using Weblate (Turkish)
Currently translated at 27.2% (94 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-11-22 17:07:43 +01:00
liushuyu011
71a77250d0 Translated using Weblate (Chinese (Simplified))
Currently translated at 58.6% (202 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2018-11-20 07:08:32 +01:00
MESUT AKCAN
af32ef24db Translated using Weblate (Turkish)
Currently translated at 20.3% (70 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-11-19 01:22:15 +01:00
GoodMirek
214dca1d4e Translated using Weblate (Czech)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2018-11-17 23:07:38 +01:00
C. Rüdinger
af8ca15e29 Translated using Weblate (German)
Currently translated at 98.6% (340 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-11-15 14:07:39 +01:00
MESUT AKCAN
ac71aec39d Translated using Weblate (Turkish)
Currently translated at 13.6% (47 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2018-11-13 19:07:48 +01:00
wellinkstein
9b7b363b49 Translated using Weblate (French)
Currently translated at 98.0% (338 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-11-13 19:07:44 +01:00
xanadus1
948b54dc58 Translated using Weblate (Chinese (Simplified))
Currently translated at 37.4% (129 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2018-11-13 19:07:38 +01:00
MESUT AKCAN
20553713a8 Added translation using Weblate (Turkish) 2018-11-11 14:14:56 +01:00
TasogareRiiku
1424001ffd Translated using Weblate (Portuguese (Brazil))
Currently translated at 41.7% (144 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2018-11-09 15:07:50 +01:00
Allan Nordhøy
fa1c49aaf2 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (342 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2018-11-09 15:07:50 +01:00
Krombel
316369f117 Translated using Weblate (German)
Currently translated at 97.4% (336 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-11-09 15:07:49 +01:00
ssantos
2253d3c2cc Translated using Weblate (German)
Currently translated at 97.4% (336 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-11-09 15:07:48 +01:00
Kunzisoft
b09bf68b7c Translated using Weblate (French)
Currently translated at 50.4% (174 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-11-09 15:07:47 +01:00
jan madsen
526111af98 Translated using Weblate (Danish)
Currently translated at 98.6% (340 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2018-11-09 15:07:47 +01:00
WaldiS
5942a33f42 Translated using Weblate (Polish)
Currently translated at 100.0% (345 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2018-11-04 17:24:15 +01:00
Allan Nordhøy
62b077c4b2 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (342 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb/
2018-11-04 17:24:11 +01:00
C. Rüdinger
89996f09f1 Translated using Weblate (German)
Currently translated at 90.4% (312 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-11-04 17:24:10 +01:00
Kunzisoft
4b760c6361 Translated using Weblate (French)
Currently translated at 50.1% (173 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-11-04 17:24:07 +01:00
Heimen Stoffels
293b7e3a05 Translated using Weblate (Dutch)
Currently translated at 77.9% (269 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2018-11-04 17:24:06 +01:00
jan madsen
12686ecb83 Translated using Weblate (Danish)
Currently translated at 79.4% (274 of 345 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2018-11-04 17:24:04 +01:00
J-Jamet
456912b134 Merge tag '2.5.0.0beta18' into develop
2.5.0.0beta18
2018-11-01 17:46:17 +01:00
somkun
9558fcaf21 Add Read support for TOTP Tokens 2018-09-16 22:11:23 -07:00
866 changed files with 49462 additions and 43283 deletions

44
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,44 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
** Keepass Database **
- Created with: [e.g Windows KeePass 2.42]
- Version: [e.g. 2]
- Location: [e.g. Remote file retrieved with GDrive app]
- Size: [e.g. 150Mo]
- Contains attachment: [e.g. Yes]
**KeePassDX (please complete the following information):**
- Version: [e.g. 2.5.0.0beta23]
- Build: [e.g. Free]
- Language: [e.g. French]
**Android (please complete the following information):**
- Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1]
**Additional context**
Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

7
.gitignore vendored
View File

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

View File

@@ -1,3 +1,72 @@
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)
* Show history of each entry
* Auto repair database for nodes with same UUID
* Management of expired nodes
* Multi-selection for actions (Cut - Copy - Delete)
* Open/Save database as service / Add persistent notification
* Fix settings / edit group / small bugs
KeepassDX (2.5.0.0beta23)
* New, more secure database creation workflow
* Recognize more database files
* Add alias for history files (WARNING: history is erased)
* New Biometric unlock (Fingerprint with new API)
* Fix entry references
* Fix OOM with KeyFile
* Fix small issues
KeepassDX (2.5.0.0beta22)
* Rebuild code for actions
* Add UUID as entry view
* Fix bug with natural order
* Fix number of entries in databaseV1
* New entry views
KeepassDX (2.5.0.0beta21)
* Fix nested groups no longer visible in V1 databases
* Improved data import algorithm for V1 databases
* Add natural database sort
* Add username database sort
* Fix button disabled with only KeyFile
* Show the number of entries in a group
KeepassDX (2.5.0.0beta20)
* Fix a major bug that displays an entry history
KeepassDX (2.5.0.0beta19)
* Add lock button always visible
* New connection workflow
* Code refactored in Kotlin
* Better notification implementation
* Better views for large screen
* Magikeyboard enhancement
* Fix Recycle Bin
* Fix memory when load database
* Fix small bugs
KeepassDX (2.5.0.0beta18)
* New recent databases views
* New information dialog

View File

@@ -1,45 +0,0 @@
Original author:
Brian Pellin
Achim Weimert
Johan Berts - search patches
Mike Mohr - Better native code for aes and sha
Tobias Selig - icon support
Tolga Onbay, Dirk Bergstrom - password generator
Space Cowboy - holo theme
josefwells
Nicholas FitzRoy-Dale - auto launch intents
yulin2 - responsiveness improvements
Tadashi Saito
vhschlenker
bumper314 - Samsung multiwindow support
Hans Cappelle - fingerprint sensor integration
Jeremy Jamet - Keepass DX Material Design - Patches
Translations:
Diego Pierotto - Italian
Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French
Maciej Bieniek, cod3r - Polish
Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian
MaWi, rvs2008, meviox, MaDill, EdlerProgrammierer, Jan Thomas - German
yslandro - Norwegian Nynorsk
王科峰 - Chinese
Typhoon - Slovak
Masahiro Inamura - Japanese
Matsuu Takuto - Japanese
Carlos Schlyter - Portugese (Brazil)
YSmhXQDd6Z - Portugese (Portugal)
andriykopanytsia - Ukranian
intel, Zoltán Antal - Hungarian
H Vanek - Czech
jipanos - Spanish
Erik Fdevriendt, Erik Jan Meijer - Dutch
Frederik Svarre - Danish
Oriol Garrote - Catalan
Mika Takala - Finnish
Niclas Burgren - Swedish
Raimonds - Latvian
dgarciabad - Basque
Arthur Zamarin - Hebrew
RaptorTFX - Greek
zygimantus - Lithuanian

View File

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

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,93 @@
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

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

1
_config.yml Normal file
View File

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

1
app/.gitignore vendored
View File

@@ -1 +1,2 @@
.cxx
.externalNativeBuild

View File

@@ -1,15 +1,18 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 27
buildToolsVersion '28.0.2'
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 27
versionCode = 18
versionName = "2.5.0.0beta18"
targetSdkVersion 28
versionCode = 26
versionName = "2.5.0.0beta26"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -24,7 +27,6 @@ android {
}
}
buildTypes {
release {
minifyEnabled = false
@@ -76,40 +78,35 @@ android {
}
}
def supportVersion = "27.1.1"
def spongycastleVersion = "1.58.0.0"
def permissionDispatcherVersion = "3.1.0"
def room_version = "2.2.1"
dependencies {
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:preference-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.biometric:biometric:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Expandable view
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
// Time
implementation 'joda-time:joda-time:2.9.9'
implementation 'org.sufficientlysecure:html-textview:3.5'
implementation 'com.nononsenseapps:filepicker:4.1.0'
// Color
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
// Education
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
// Permissions
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
// if you don't use android.app.Fragment you can exclude support for them
exclude module: "support-v13"
}
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
// Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2'
// Base64
implementation 'biz.source_code:base64coder:2010-12-19'
// IO-Extras
implementation 'com.github.davidmoten:io-extras:0.1'
implementation 'com.google.code.gson:gson:2.8.4'
implementation 'com.google.guava:guava:23.0-android'
// Apache Commons Codec
implementation 'commons-codec:commons-codec:1.11'
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')

View File

@@ -1,42 +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.tests;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.tests.database.TestData;
public class AccentTest extends AndroidTestCase {
private static final String KEYFILE = "";
private static final String PASSWORD = "é";
private static final String ASSET = "accent.kdb";
private static final String FILENAME = "/sdcard/accent.kdb";
public void testOpen() {
try {
TestData.GetDb(getContext(), ASSET, PASSWORD, KEYFILE, FILENAME);
} catch (Exception e) {
assertTrue("Failed to open database", false);
}
}
}

View File

@@ -1,34 +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.tests;
import junit.framework.Test;
import junit.framework.TestSuite;
import android.test.suitebuilder.TestSuiteBuilder;
public class AllTests extends TestSuite {
public static Test suite() {
return new TestSuiteBuilder(AllTests.class)
.includeAllPackagesUnderHere()
.build();
}
}

View File

@@ -1,35 +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.tests;
import junit.framework.Test;
import junit.framework.TestSuite;
import android.test.suitebuilder.TestSuiteBuilder;
public class OutputTests extends TestSuite {
public static Test suite() {
return new TestSuiteBuilder(AllTests.class)
.includePackages("com.kunzisoft.keepass.tests.output")
.build();
}
}

View File

@@ -1,38 +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.tests;
import junit.framework.TestCase;
import com.kunzisoft.keepass.database.PwDate;
public class PwDateTest extends TestCase {
public void testDate() {
PwDate jDate = new PwDate(System.currentTimeMillis());
PwDate intermediate = (PwDate) jDate.clone();
PwDate cDate = new PwDate(intermediate.getCDate(), 0);
assertTrue("jDate and intermediate not equal", jDate.equals(intermediate));
assertTrue("jDate and cDate not equal", cDate.equals(jDate));
}
}

View File

@@ -1,63 +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.tests;
import static org.junit.Assert.assertArrayEquals;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.tests.database.TestData;
public class PwEntryTestV3 extends AndroidTestCase {
PwEntryV3 mPE;
@Override
protected void setUp() throws Exception {
super.setUp();
mPE = (PwEntryV3) TestData.GetTest1(getContext()).getEntryAt(0);
}
public void testName() {
assertTrue("Name was " + mPE.getTitle(), mPE.getTitle().equals("Amazon"));
}
public void testPassword() throws UnsupportedEncodingException {
String sPass = "12345";
byte[] password = sPass.getBytes("UTF-8");
assertArrayEquals(password, mPE.getPasswordBytes());
}
public void testCreation() {
Calendar cal = Calendar.getInstance();
cal.setTime(mPE.getCreationTime().getDate());
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);
assertEquals("Incorrect day.", cal.get(Calendar.DAY_OF_MONTH), 23);
}
}

View File

@@ -1,67 +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.tests;
import com.kunzisoft.keepass.database.AutoType;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.database.PwIconCustom;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.security.ProtectedBinary;
import com.kunzisoft.keepass.database.security.ProtectedString;
import junit.framework.TestCase;
import java.util.UUID;
public class PwEntryTestV4 extends TestCase {
public void testAssign() {
PwEntryV4 entry = new PwEntryV4();
entry.setAdditional("test223");
entry.setAutoType(new AutoType());
entry.getAutoType().defaultSequence = "1324";
entry.getAutoType().enabled = true;
entry.getAutoType().obfuscationOptions = 123412432109L;
entry.getAutoType().put("key", "value");
entry.setBackgroupColor("blue");
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
entry.setIconCustom(new PwIconCustom(UUID.randomUUID(), new byte[0]));
entry.setForegroundColor("red");
entry.addToHistory(new PwEntryV4());
entry.setIconStandard(new PwIconStandard(5));
entry.setOverrideURL("override");
entry.setParent(new PwGroupV4());
entry.addExtraField("key2", new ProtectedString(false, "value2"));
entry.setUrl("http://localhost");
entry.setUUID(UUID.randomUUID());
PwEntryV4 target = new PwEntryV4();
target.updateWith(entry);
/* This test is not so useful now that I am not implementing value equality for Entries
assertTrue("Entries do not match.", entry.equals(target));
*/
}
}

View File

@@ -1,44 +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.tests;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.PwGroupV3;
import com.kunzisoft.keepass.tests.database.TestData;
public class PwGroupTest extends AndroidTestCase {
PwGroupV3 mPG;
@Override
protected void setUp() throws Exception {
super.setUp();
mPG = (PwGroupV3) TestData.GetTest1(getContext()).getGroups().get(0);
}
public void testGroupName() {
assertTrue("Name was " + mPG.getName(), mPG.getName().equals("Internet"));
}
}

View File

@@ -0,0 +1,195 @@
/*
* 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.tests
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.*
class StringDatabaseKDBUtilsTest : TestCase() {
fun testReadWriteLongZero() {
testReadWriteLong(0.toByte())
}
fun testReadWriteLongMax() {
testReadWriteLong(java.lang.Byte.MAX_VALUE)
}
fun testReadWriteLongMin() {
testReadWriteLong(java.lang.Byte.MIN_VALUE)
}
fun testReadWriteLongRnd() {
val rnd = Random()
val buf = ByteArray(1)
rnd.nextBytes(buf)
testReadWriteLong(buf[0])
}
private fun testReadWriteLong(value: Byte) {
val orig = ByteArray(8)
setArray(orig, value, 8)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
}
fun testReadWriteIntZero() {
testReadWriteInt(0.toByte())
}
fun testReadWriteIntMin() {
testReadWriteInt(java.lang.Byte.MIN_VALUE)
}
fun testReadWriteIntMax() {
testReadWriteInt(java.lang.Byte.MAX_VALUE)
}
private fun testReadWriteInt(value: Byte) {
val orig = ByteArray(4)
for (i in 0..3) {
orig[i] = 0
}
setArray(orig, value, 4)
val one = bytes4ToInt(orig)
val dest = intTo4Bytes(one)
assertArrayEquals(orig, dest)
}
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
for (i in 0 until size) {
buf[i] = value
}
}
fun testReadWriteShortOne() {
val orig = ByteArray(2)
orig[0] = 0
orig[1] = 1
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest)
}
fun testReadWriteShortMin() {
testReadWriteShort(java.lang.Byte.MIN_VALUE)
}
fun testReadWriteShortMax() {
testReadWriteShort(java.lang.Byte.MAX_VALUE)
}
private fun testReadWriteShort(value: Byte) {
val orig = ByteArray(2)
setArray(orig, value, 2)
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest)
}
fun testReadWriteByteZero() {
testReadWriteByte(0.toByte())
}
fun testReadWriteByteMin() {
testReadWriteByte(java.lang.Byte.MIN_VALUE)
}
fun testReadWriteByteMax() {
testReadWriteShort(java.lang.Byte.MAX_VALUE)
}
private fun testReadWriteByte(value: Byte) {
val dest: Byte = uIntToByte(byteToUInt(value))
assert(value == dest)
}
fun testDate() {
val cal = Calendar.getInstance()
val expected = Calendar.getInstance()
expected.set(2008, 1, 2, 3, 4, 5)
val actual = Calendar.getInstance()
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: ", 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 bUUID = ByteArray(16)
Random().nextBytes(bUUID)
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)
fun testULongMax() {
val ulongBytes = ByteArray(8)
for (i in ulongBytes.indices) {
ulongBytes[i] = -1
}
val bos = ByteArrayOutputStream()
val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(ULONG_MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()
assertArrayEquals(ulongBytes, uLongMax)
}
}

View File

@@ -1,71 +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.tests;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Environment;
import com.kunzisoft.keepass.utils.EmptyUtils;
import com.kunzisoft.keepass.utils.UriUtil;
public class TestUtil {
private static final File sdcard = Environment.getExternalStorageDirectory();
public static void extractKey(Context ctx, String asset, String target) throws Exception {
InputStream key = ctx.getAssets().open(asset, AssetManager.ACCESS_STREAMING);
FileOutputStream keyFile = new FileOutputStream(target);
while (true) {
byte[] buf = new byte[1024];
int read = key.read(buf);
if ( read == -1 ) {
break;
} else {
keyFile.write(buf, 0, read);
}
}
keyFile.close();
}
public static InputStream getKeyFileInputStream(Context ctx, String keyfile) throws FileNotFoundException {
InputStream keyIs = null;
if (!EmptyUtils.isNullOrEmpty(keyfile)) {
Uri uri = UriUtil.parseDefaultFile(keyfile);
keyIs = UriUtil.getUriInputStream(ctx, uri);
}
return keyIs;
}
public static String getSdPath(String filename) {
File file = new File(sdcard, filename);
return file.getAbsolutePath();
}
}

View File

@@ -1,211 +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.tests;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayOutputStream;
import java.util.Calendar;
import java.util.Random;
import java.util.UUID;
import junit.framework.TestCase;
import com.kunzisoft.keepass.database.PwDate;
import com.kunzisoft.keepass.stream.LEDataInputStream;
import com.kunzisoft.keepass.stream.LEDataOutputStream;
import com.kunzisoft.keepass.utils.Types;
public class TypesTest extends TestCase {
public void testReadWriteLongZero() {
testReadWriteLong((byte) 0);
}
public void testReadWriteLongMax() {
testReadWriteLong(Byte.MAX_VALUE);
}
public void testReadWriteLongMin() {
testReadWriteLong(Byte.MIN_VALUE);
}
public void testReadWriteLongRnd() {
Random rnd = new Random();
byte[] buf = new byte[1];
rnd.nextBytes(buf);
testReadWriteLong(buf[0]);
}
private void testReadWriteLong(byte value) {
byte[] orig = new byte[8];
byte[] dest = new byte[8];
setArray(orig, value, 0, 8);
long one = LEDataInputStream.readLong(orig, 0);
LEDataOutputStream.writeLong(one, dest, 0);
assertArrayEquals(orig, dest);
}
public void testReadWriteIntZero() {
testReadWriteInt((byte) 0);
}
public void testReadWriteIntMin() {
testReadWriteInt(Byte.MIN_VALUE);
}
public void testReadWriteIntMax() {
testReadWriteInt(Byte.MAX_VALUE);
}
private void testReadWriteInt(byte value) {
byte[] orig = new byte[4];
byte[] dest = new byte[4];
for (int i = 0; i < 4; i++ ) {
orig[i] = 0;
}
setArray(orig, value, 0, 4);
int one = LEDataInputStream.readInt(orig, 0);
LEDataOutputStream.writeInt(one, dest, 0);
assertArrayEquals(orig, dest);
}
private void setArray(byte[] buf, byte value, int offset, int size) {
for (int i = offset; i < offset + size; i++) {
buf[i] = value;
}
}
public void testReadWriteShortOne() {
byte[] orig = new byte[2];
byte[] dest = new byte[2];
orig[0] = 0;
orig[1] = 1;
int one = LEDataInputStream.readUShort(orig, 0);
dest = LEDataOutputStream.writeUShortBuf(one);
assertArrayEquals(orig, dest);
}
public void testReadWriteShortMin() {
testReadWriteShort(Byte.MIN_VALUE);
}
public void testReadWriteShortMax() {
testReadWriteShort(Byte.MAX_VALUE);
}
private void testReadWriteShort(byte value) {
byte[] orig = new byte[2];
byte[] dest = new byte[2];
setArray(orig, value, 0, 2);
int one = LEDataInputStream.readUShort(orig, 0);
LEDataOutputStream.writeUShort(one, dest, 0);
assertArrayEquals(orig, dest);
}
public void testReadWriteByteZero() {
testReadWriteByte((byte) 0);
}
public void testReadWriteByteMin() {
testReadWriteByte(Byte.MIN_VALUE);
}
public void testReadWriteByteMax() {
testReadWriteShort(Byte.MAX_VALUE);
}
private void testReadWriteByte(byte value) {
byte[] orig = new byte[1];
byte[] dest = new byte[1];
setArray(orig, value, 0, 1);
int one = Types.readUByte(orig, 0);
Types.writeUByte(one, dest, 0);
assertArrayEquals(orig, dest);
}
public void testDate() {
Calendar cal = Calendar.getInstance();
Calendar expected = Calendar.getInstance();
expected.set(2008, 1, 2, 3, 4, 5);
byte[] buf = PwDate.writeTime(expected.getTime(), cal);
Calendar actual = Calendar.getInstance();
actual.setTime(PwDate.readTime(buf, 0, cal));
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("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));
}
public void testUUID() {
Random rnd = new Random();
byte[] bUUID = new byte[16];
rnd.nextBytes(bUUID);
UUID uuid = Types.bytestoUUID(bUUID);
byte[] eUUID = Types.UUIDtoBytes(uuid);
assertArrayEquals("UUID match failed", bUUID, eUUID);
}
public void testULongMax() throws Exception {
byte[] ulongBytes = new byte[8];
for (int i = 0; i < ulongBytes.length; i++) {
ulongBytes[i] = -1;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
LEDataOutputStream leos = new LEDataOutputStream(bos);
leos.writeLong(Types.ULONG_MAX_VALUE);
leos.close();
byte[] uLongMax = bos.toByteArray();
assertArrayEquals(ulongBytes, uLongMax);
}
}

View File

@@ -1,83 +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.tests.crypto;
import com.kunzisoft.keepass.crypto.CipherFactory;
import junit.framework.TestCase;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import static org.junit.Assert.assertArrayEquals;
public class AESTest extends TestCase {
private Random mRand = new Random();
public void testEncrypt() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
// Test above below and at the blocksize
testFinal(15);
testFinal(16);
testFinal(17);
// Test random larger sizes
int size = mRand.nextInt(494) + 18;
testFinal(size);
}
private void testFinal(int dataSize) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
// Generate some input
byte[] input = new byte[dataSize];
mRand.nextBytes(input);
// Generate key
byte[] keyArray = new byte[32];
mRand.nextBytes(keyArray);
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
// Generate IV
byte[] ivArray = new byte[16];
mRand.nextBytes(ivArray);
IvParameterSpec iv = new IvParameterSpec(ivArray);
Cipher android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true);
android.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] outAndroid = android.doFinal(input, 0, dataSize);
Cipher nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding");
nat.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] outNative = nat.doFinal(input, 0, dataSize);
assertArrayEquals("Arrays differ on size: " + dataSize, outAndroid, outNative);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
import com.kunzisoft.keepass.crypto.CipherFactory
import junit.framework.TestCase
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.Random
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import org.junit.Assert.assertArrayEquals
class AESTest : TestCase() {
private val mRand = Random()
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidAlgorithmParameterException::class)
fun testEncrypt() {
// Test above below and at the blocksize
testFinal(15)
testFinal(16)
testFinal(17)
// Test random larger sizes
val size = mRand.nextInt(494) + 18
testFinal(size)
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
private fun testFinal(dataSize: Int) {
// Generate some input
val input = ByteArray(dataSize)
mRand.nextBytes(input)
// Generate key
val keyArray = ByteArray(32)
mRand.nextBytes(keyArray)
val key = SecretKeySpec(keyArray, "AES")
// Generate IV
val ivArray = ByteArray(16)
mRand.nextBytes(ivArray)
val iv = IvParameterSpec(ivArray)
val android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true)
android.init(Cipher.ENCRYPT_MODE, key, iv)
val outAndroid = android.doFinal(input, 0, dataSize)
val nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding")
nat.init(Cipher.ENCRYPT_MODE, key, iv)
val outNative = nat.doFinal(input, 0, dataSize)
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
}
}

View File

@@ -1,100 +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.tests.crypto;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
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;
public class CipherTest extends TestCase {
private Random rand = new Random();
public void testCipherFactory() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] key = new byte[32];
byte[] iv = new byte[16];
byte[] plaintext = new byte[1024];
rand.nextBytes(key);
rand.nextBytes(iv);
rand.nextBytes(plaintext);
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
byte[] secrettext = encrypt.doFinal(plaintext);
byte[] decrypttext = decrypt.doFinal(secrettext);
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
}
public void testCipherStreams() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {
final int MESSAGE_LENGTH = 1024;
byte[] key = new byte[32];
byte[] iv = new byte[16];
byte[] plaintext = new byte[MESSAGE_LENGTH];
rand.nextBytes(key);
rand.nextBytes(iv);
rand.nextBytes(plaintext);
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
CipherOutputStream cos = new CipherOutputStream(bos, encrypt);
cos.write(plaintext);
cos.close();
byte[] secrettext = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(secrettext);
BetterCipherInputStream cis = new BetterCipherInputStream(bis, decrypt);
LEDataInputStream lis = new LEDataInputStream(cis);
byte[] decrypttext = lis.readBytes(MESSAGE_LENGTH);
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.Random
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
class CipherTest : TestCase() {
private val rand = Random()
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class)
fun testCipherFactory() {
val key = ByteArray(32)
val iv = ByteArray(16)
val plaintext = ByteArray(1024)
rand.nextBytes(key)
rand.nextBytes(iv)
rand.nextBytes(plaintext)
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
val secrettext = encrypt.doFinal(plaintext)
val decrypttext = decrypt.doFinal(secrettext)
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
}
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class, IOException::class)
fun testCipherStreams() {
val MESSAGE_LENGTH = 1024
val key = ByteArray(32)
val iv = ByteArray(16)
val plaintext = ByteArray(MESSAGE_LENGTH)
rand.nextBytes(key)
rand.nextBytes(iv)
rand.nextBytes(plaintext)
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
val bos = ByteArrayOutputStream()
val cos = CipherOutputStream(bos, encrypt)
cos.write(plaintext)
cos.close()
val secrettext = bos.toByteArray()
val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt)
val lis = LittleEndianDataInputStream(cis)
val decrypttext = lis.readBytes(MESSAGE_LENGTH)
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
}
}

View File

@@ -1,66 +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.tests.crypto;
import static org.junit.Assert.assertArrayEquals;
import java.io.IOException;
import java.util.Random;
import junit.framework.TestCase;
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey;
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey;
public class FinalKeyTest extends TestCase {
private Random mRand;
@Override
protected void setUp() throws Exception {
super.setUp();
mRand = new Random();
}
public void testNativeAndroid() throws IOException {
// Test both an old and an even number to test my flip variable
testNativeFinalKey(5);
testNativeFinalKey(6);
}
private void testNativeFinalKey(int rounds) throws IOException {
byte[] seed = new byte[32];
byte[] key = new byte[32];
byte[] nativeKey;
byte[] androidKey;
mRand.nextBytes(seed);
mRand.nextBytes(key);
AndroidFinalKey aKey = new AndroidFinalKey();
androidKey = aKey.transformMasterKey(seed, key, rounds);
NativeFinalKey nKey = new NativeFinalKey();
nativeKey = nKey.transformMasterKey(seed, key, rounds);
assertArrayEquals("Does not match", androidKey, nativeKey);
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
import org.junit.Assert.assertArrayEquals
import java.io.IOException
import java.util.Random
import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey
class FinalKeyTest : TestCase() {
private var mRand: Random? = null
@Throws(Exception::class)
override fun setUp() {
super.setUp()
mRand = Random()
}
@Throws(IOException::class)
fun testNativeAndroid() {
// Test both an old and an even number to test my flip variable
testNativeFinalKey(5)
testNativeFinalKey(6)
}
@Throws(IOException::class)
private fun testNativeFinalKey(rounds: Int) {
val seed = ByteArray(32)
val key = ByteArray(32)
val nativeKey: ByteArray
val androidKey: ByteArray
mRand!!.nextBytes(seed)
mRand!!.nextBytes(key)
val aKey = AndroidFinalKey()
androidKey = aKey.transformMasterKey(seed, key, rounds.toLong())
val nKey = NativeFinalKey()
nativeKey = nKey.transformMasterKey(seed, key, rounds.toLong())
assertArrayEquals("Does not match", androidKey, nativeKey)
}
}

View File

@@ -1,113 +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.tests.database;
import android.content.Context;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwDatabaseV3;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
import com.kunzisoft.keepass.database.search.SearchDbHelper;
import java.util.List;
public class DeleteEntry extends AndroidTestCase {
private static final String GROUP1_NAME = "Group1";
private static final String ENTRY1_NAME = "Test1";
private static final String ENTRY2_NAME = "Test2";
private static final String KEYFILE = "";
private static final String PASSWORD = "12345";
private static final String ASSET = "delete.kdb";
private static final String FILENAME = "/sdcard/delete.kdb";
public void testDelete() {
Database db;
Context ctx = getContext();
try {
db = TestData.GetDb(ctx, ASSET, PASSWORD, KEYFILE, FILENAME);
} catch (Exception e) {
assertTrue("Failed to open database: " + e.getMessage(), false);
return;
}
PwDatabaseV3 pm = (PwDatabaseV3) db.getPwDatabase();
PwGroup group1 = getGroup(pm, GROUP1_NAME);
assertNotNull("Could not find group1", group1);
// Delete the group
DeleteGroupRunnable task = new DeleteGroupRunnable(null, db, group1, null, true);
task.run();
// Verify the entries were deleted
PwEntry entry1 = getEntry(pm, ENTRY1_NAME);
assertNull("Entry 1 was not removed", entry1);
PwEntry entry2 = getEntry(pm, ENTRY2_NAME);
assertNull("Entry 2 was not removed", entry2);
// Verify the entries were removed from the search index
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
// Verify the group was deleted
group1 = getGroup(pm, GROUP1_NAME);
assertNull("Group 1 was not removed.", group1);
}
private PwEntryV3 getEntry(PwDatabaseV3 pm, String name) {
List<PwEntryV3> entries = pm.getEntries();
for ( int i = 0; i < entries.size(); i++ ) {
PwEntryV3 entry = entries.get(i);
if ( entry.getTitle().equals(name) ) {
return entry;
}
}
return null;
}
private PwGroup getGroup(PwDatabase pm, String name) {
List<PwGroup> groups = pm.getGroups();
for ( int i = 0; i < groups.size(); i++ ) {
PwGroup group = groups.get(i);
if ( group.getName().equals(name) ) {
return group;
}
}
return null;
}
}

View File

@@ -1,54 +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.tests.database;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwEntryV4;
import junit.framework.TestCase;
public class EntryV4 extends TestCase {
public void testBackup() {
PwDatabaseV4 db = new PwDatabaseV4();
db.setHistoryMaxItems(2);
PwEntryV4 entry = new PwEntryV4();
entry.startToManageFieldReferences(db);
entry.setTitle("Title1");
entry.setUsername("User1");
entry.createBackup(db);
entry.setTitle("Title2");
entry.setUsername("User2");
entry.createBackup(db);
entry.setTitle("Title3");
entry.setUsername("User3");
entry.createBackup(db);
PwEntryV4 backup = entry.getHistory().get(0);
entry.stopToManageFieldReferences();
assertEquals("Title2", backup.getTitle());
assertEquals("User2", backup.getUsername());
}
}

View File

@@ -1,62 +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.tests.database;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Environment;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.load.ImporterV3;
import com.kunzisoft.keepass.tests.TestUtil;
import com.kunzisoft.keepass.utils.UriUtil;
import java.io.InputStream;
import java.io.File;
public class Kdb3 extends AndroidTestCase {
private void testKeyfile(String dbAsset, String keyAsset, String password) throws Exception {
Context ctx = getContext();
File sdcard = Environment.getExternalStorageDirectory();
String keyPath = sdcard.getAbsolutePath() + "/key";
TestUtil.extractKey(ctx, keyAsset, keyPath);
AssetManager am = ctx.getAssets();
InputStream is = am.open(dbAsset, AssetManager.ACCESS_STREAMING);
ImporterV3 importer = new ImporterV3();
importer.openDatabase(is, password, TestUtil.getKeyFileInputStream(ctx, keyPath));
is.close();
}
public void testXMLKeyFile() throws Exception {
testKeyfile("kdb_with_xml_keyfile.kdb", "keyfile.key", "12345");
}
public void testBinary64KeyFile() throws Exception {
testKeyfile("binary-key.kdb", "binary.key", "12345");
}
}

View File

@@ -1,48 +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.tests.database;
import java.io.InputStream;
import android.content.Context;
import android.content.res.AssetManager;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.PwDatabaseV3;
import com.kunzisoft.keepass.database.PwEncryptionAlgorithm;
import com.kunzisoft.keepass.database.load.ImporterV3;
public class Kdb3Twofish extends AndroidTestCase {
public void testReadTwofish() throws Exception {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("twofish.kdb", AssetManager.ACCESS_STREAMING);
ImporterV3 importer = new ImporterV3();
PwDatabaseV3 db = importer.openDatabase(is, "12345", null);
assertTrue(db.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish);
is.close();
}
}

View File

@@ -1,171 +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.tests.database;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.content.Context;
import android.content.res.AssetManager;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.exception.InvalidDBException;
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
import com.kunzisoft.keepass.database.load.Importer;
import com.kunzisoft.keepass.database.load.ImporterFactory;
import com.kunzisoft.keepass.database.load.ImporterV4;
import com.kunzisoft.keepass.database.save.PwDbOutput;
import com.kunzisoft.keepass.database.save.PwDbV4Output;
import com.kunzisoft.keepass.stream.CopyInputStream;
import com.kunzisoft.keepass.tests.TestUtil;
public class Kdb4 extends AndroidTestCase {
public void testDetection() throws IOException, InvalidDBException {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
Importer importer = ImporterFactory.createImporter(is);
assertTrue(importer instanceof ImporterV4);
is.close();
}
public void testParsing() throws IOException, InvalidDBException {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
importer.openDatabase(is, "12345", null);
is.close();
}
public void testSavingKDBXV3() throws IOException, InvalidDBException, PwDbOutputException {
testSaving("test.kdbx", "12345", "test-out.kdbx");
}
public void testSavingKDBXV4() throws IOException, InvalidDBException, PwDbOutputException {
testSaving("test-kdbxv4.kdbx", "1", "test-kdbxv4-out.kdbx");
}
private void testSaving(String inputFile, String password, String outputFile) throws IOException, InvalidDBException, PwDbOutputException {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open(inputFile, AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
PwDatabaseV4 db = importer.openDatabase(is, password, null);
is.close();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PwDbV4Output output = (PwDbV4Output) PwDbOutput.getInstance(db, bos);
output.output();
byte[] data = bos.toByteArray();
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath(outputFile), false);
InputStream bis = new ByteArrayInputStream(data);
bis = new CopyInputStream(bis, fos);
importer = new ImporterV4();
db = importer.openDatabase(bis, password, null);
bis.close();
fos.close();
}
@Override
protected void setUp() throws Exception {
super.setUp();
TestUtil.extractKey(getContext(), "keyfile.key", TestUtil.getSdPath("key"));
TestUtil.extractKey(getContext(), "binary.key", TestUtil.getSdPath("key-binary"));
}
public void testComposite() throws IOException, InvalidDBException {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("keyfile.kdbx", AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
importer.openDatabase(is, "12345", TestUtil.getKeyFileInputStream(ctx, TestUtil.getSdPath("key")));
is.close();
}
public void testCompositeBinary() throws IOException, InvalidDBException {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("keyfile-binary.kdbx", AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
importer.openDatabase(is, "12345", TestUtil.getKeyFileInputStream(ctx,TestUtil.getSdPath("key-binary")));
is.close();
}
public void testKeyfile() throws IOException, InvalidDBException {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("key-only.kdbx", AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
importer.openDatabase(is, "", TestUtil.getKeyFileInputStream(ctx, TestUtil.getSdPath("key")));
is.close();
}
public void testNoGzip() throws IOException, InvalidDBException {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("no-encrypt.kdbx", AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
importer.openDatabase(is, "12345", null);
is.close();
}
}

View File

@@ -1,50 +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.tests.database;
import android.content.Context;
import android.content.res.AssetManager;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.crypto.engine.AesEngine;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.load.ImporterV4;
import java.io.InputStream;
public class Kdb4Header extends AndroidTestCase {
public void testReadHeader() throws Exception {
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
PwDatabaseV4 db = importer.openDatabase(is, "12345", null);
assertEquals(6000, db.getNumberKeyEncryptionRounds());
assertTrue(db.getDataCipher().equals(AesEngine.CIPHER_UUID));
is.close();
}
}

View File

@@ -1,80 +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.tests.database;
import java.io.InputStream;
import java.util.UUID;
import android.content.Context;
import android.content.res.AssetManager;
import android.test.AndroidTestCase;
import biz.source_code.base64Coder.Base64Coder;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.load.ImporterV4;
import com.kunzisoft.keepass.utils.SprEngineV4;
import com.kunzisoft.keepass.utils.Types;
public class SprEngineTest extends AndroidTestCase {
private PwDatabaseV4 db;
private SprEngineV4 spr;
@Override
protected void setUp() throws Exception {
super.setUp();
Context ctx = getContext();
AssetManager am = ctx.getAssets();
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
ImporterV4 importer = new ImporterV4();
db = importer.openDatabase(is, "12345", null);
is.close();
spr = new SprEngineV4();
}
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
private final String ENCODE_UUID = "IN7RkON49Ui1UZ2ddqmLcw==";
private final String RESULT = "Password";
public void testRefReplace() {
UUID entryUUID = decodeUUID(ENCODE_UUID);
PwEntryV4 entry = (PwEntryV4) db.getEntryByUUIDId(entryUUID);
assertEquals(RESULT, spr.compile(REF, entry, db));
}
private UUID decodeUUID(String encoded) {
if (encoded == null || encoded.length() == 0 ) {
return PwDatabase.UUID_ZERO;
}
byte[] buf = Base64Coder.decode(encoded);
return Types.bytestoUUID(buf);
}
}

View File

@@ -1,77 +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.tests.database;
import java.io.InputStream;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabaseV3Debug;
import com.kunzisoft.keepass.database.load.Importer;
import com.kunzisoft.keepass.tests.TestUtil;
public class TestData {
private static final String TEST1_KEYFILE = "";
private static final String TEST1_KDB = "test1.kdb";
private static final String TEST1_PASSWORD = "12345";
private static Database mDb1;
public static Database GetDb1(Context ctx) throws Exception {
return GetDb1(ctx, false);
}
public static Database GetDb1(Context ctx, boolean forceReload) throws Exception {
if ( mDb1 == null || forceReload ) {
mDb1 = GetDb(ctx, TEST1_KDB, TEST1_PASSWORD, TEST1_KEYFILE, "/sdcard/test1.kdb");
}
return mDb1;
}
public static Database GetDb(Context ctx, String asset, String password, String keyfile, String filename) throws Exception {
AssetManager am = ctx.getAssets();
InputStream is = am.open(asset, AssetManager.ACCESS_STREAMING);
Database Db = new Database();
InputStream keyIs = TestUtil.getKeyFileInputStream(ctx, keyfile);
Db.loadData(ctx, is, password, keyIs, Importer.DEBUG);
Uri.Builder b = new Uri.Builder();
Db.setUri(b.scheme("file").path(filename).build());
return Db;
}
public static PwDatabaseV3Debug GetTest1(Context ctx) throws Exception {
if ( mDb1 == null ) {
GetDb1(ctx);
}
return (PwDatabaseV3Debug) mDb1.getPwDatabase();
}
}

View File

@@ -1,146 +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.tests.output;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.content.res.AssetManager;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.PwDatabaseV3Debug;
import com.kunzisoft.keepass.database.PwDbHeader;
import com.kunzisoft.keepass.database.PwDbHeaderV3;
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
import com.kunzisoft.keepass.database.save.PwDbHeaderOutputV3;
import com.kunzisoft.keepass.database.save.PwDbV3Output;
import com.kunzisoft.keepass.database.save.PwDbV3OutputDebug;
import com.kunzisoft.keepass.stream.NullOutputStream;
import com.kunzisoft.keepass.tests.TestUtil;
import com.kunzisoft.keepass.tests.database.TestData;
public class PwManagerOutputTest extends AndroidTestCase {
PwDatabaseV3Debug mPM;
@Override
protected void setUp() throws Exception {
super.setUp();
mPM = TestData.GetTest1(getContext());
}
public void testPlainContent() throws IOException, PwDbOutputException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PwDbV3Output pos = new PwDbV3OutputDebug(mPM, bos, true);
pos.outputPlanGroupAndEntries(bos);
assertTrue("No output", bos.toByteArray().length > 0);
assertArrayEquals("Group and entry output doesn't match.", mPM.getPostHeader(), bos.toByteArray());
}
public void testChecksum() throws NoSuchAlgorithmException, IOException, PwDbOutputException {
//FileOutputStream fos = new FileOutputStream("/dev/null");
NullOutputStream nos = new NullOutputStream();
MessageDigest md = MessageDigest.getInstance("SHA-256");
DigestOutputStream dos = new DigestOutputStream(nos, md);
PwDbV3Output pos = new PwDbV3OutputDebug(mPM, dos, true);
pos.outputPlanGroupAndEntries(dos);
dos.close();
byte[] digest = md.digest();
assertTrue("No output", digest.length > 0);
assertArrayEquals("Hash of groups and entries failed.", mPM.getDbHeader().contentsHash, digest);
}
private void assertHeadersEquals(PwDbHeaderV3 expected, PwDbHeaderV3 actual) {
assertEquals("Flags unequal", expected.flags, actual.flags);
assertEquals("Entries unequal", expected.numEntries, actual.numEntries);
assertEquals("Groups unequal", expected.numGroups, actual.numGroups);
assertEquals("Key Rounds unequal", expected.numKeyEncRounds, actual.numKeyEncRounds);
assertEquals("Signature1 unequal", expected.signature1, actual.signature1);
assertEquals("Signature2 unequal", expected.signature2, actual.signature2);
assertTrue("Version incompatible", PwDbHeaderV3.compatibleHeaders(expected.version, actual.version));
assertArrayEquals("Hash unequal", expected.contentsHash, actual.contentsHash);
assertArrayEquals("IV unequal", expected.encryptionIV, actual.encryptionIV);
assertArrayEquals("Seed unequal", expected.masterSeed, actual.masterSeed);
assertArrayEquals("Seed2 unequal", expected.transformSeed, actual.transformSeed);
}
public void testHeader() throws PwDbOutputException, IOException {
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
PwDbHeaderV3 header = pActual.outputHeader(bActual);
ByteArrayOutputStream bExpected = new ByteArrayOutputStream();
PwDbHeaderOutputV3 outExpected = new PwDbHeaderOutputV3(mPM.getDbHeader(), bExpected);
outExpected.output();
assertHeadersEquals(mPM.getDbHeader(), header);
assertTrue("No output", bActual.toByteArray().length > 0);
assertArrayEquals("Header does not match.", bExpected.toByteArray(), bActual.toByteArray());
}
public void testFinalKey() throws PwDbOutputException {
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
PwDbHeader hActual = pActual.outputHeader(bActual);
byte[] finalKey = pActual.getFinalKey(hActual);
assertArrayEquals("Keys mismatched", mPM.getFinalKey(), finalKey);
}
public void testFullWrite() throws IOException, PwDbOutputException {
AssetManager am = getContext().getAssets();
InputStream is = am.open("test1.kdb");
// Pull file into byte array (for streaming fun)
ByteArrayOutputStream bExpected = new ByteArrayOutputStream();
while (true) {
int data = is.read();
if ( data == -1 ) {
break;
}
bExpected.write(data);
}
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
pActual.output();
//pActual.close();
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath("test1_out.kdb"));
fos.write(bActual.toByteArray());
fos.close();
assertArrayEquals("Databases do not match.", bExpected.toByteArray(), bActual.toByteArray());
}
}

View File

@@ -1,72 +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.tests.search;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.tests.database.TestData;
public class SearchTest extends AndroidTestCase {
private Database mDb;
@Override
protected void setUp() throws Exception {
super.setUp();
mDb = TestData.GetDb1(getContext(), true);
}
public void testSearch() {
PwGroup results = mDb.search("Amazon");
assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
}
public void testBackupIncluded() {
updateOmitSetting(false);
PwGroup results = mDb.search("BackupOnly");
assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
}
public void testBackupExcluded() {
updateOmitSetting(true);
PwGroup results = mDb.search("BackupOnly");
assertFalse("Search result found, but should not have been.", results.numbersOfChildEntries() > 0);
}
private void updateOmitSetting(boolean setting) {
Context ctx = getContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("settings_omitbackup_key", setting);
editor.commit();
}
}

View File

@@ -1,110 +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.tests.stream;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import junit.framework.TestCase;
import com.kunzisoft.keepass.stream.HashedBlockInputStream;
import com.kunzisoft.keepass.stream.HashedBlockOutputStream;
public class HashedBlock extends TestCase {
private static Random rand = new Random();
public void testBlockAligned() throws IOException {
testSize(1024, 1024);
}
public void testOffset() throws IOException {
testSize(1500, 1024);
}
private void testSize(int blockSize, int bufferSize) throws IOException {
byte[] orig = new byte[blockSize];
rand.nextBytes(orig);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HashedBlockOutputStream output = new HashedBlockOutputStream(bos, bufferSize);
output.write(orig);
output.close();
byte[] encoded = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
HashedBlockInputStream input = new HashedBlockInputStream(bis);
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
while ( true ) {
byte[] buf = new byte[1024];
int read = input.read(buf);
if ( read == -1 ) {
break;
}
decoded.write(buf, 0, read);
}
byte[] out = decoded.toByteArray();
assertArrayEquals(orig, out);
}
public void testGZIPStream() throws IOException {
final int testLength = 32000;
byte[] orig = new byte[testLength];
rand.nextBytes(orig);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HashedBlockOutputStream hos = new HashedBlockOutputStream(bos);
GZIPOutputStream zos = new GZIPOutputStream(hos);
zos.write(orig);
zos.close();
byte[] compressed = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
HashedBlockInputStream his = new HashedBlockInputStream(bis);
GZIPInputStream zis = new GZIPInputStream(his);
byte[] uncompressed = new byte[testLength];
int read = 0;
while (read != -1 && testLength - read > 0) {
read += zis.read(uncompressed, read, testLength - read);
}
assertArrayEquals("Output not equal to input", orig, uncompressed);
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.stream
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.Random
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import junit.framework.TestCase
import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
class HashedBlock : TestCase() {
@Throws(IOException::class)
fun testBlockAligned() {
testSize(1024, 1024)
}
@Throws(IOException::class)
fun testOffset() {
testSize(1500, 1024)
}
@Throws(IOException::class)
private fun testSize(blockSize: Int, bufferSize: Int) {
val orig = ByteArray(blockSize)
rand.nextBytes(orig)
val bos = ByteArrayOutputStream()
val output = HashedBlockOutputStream(bos, bufferSize)
output.write(orig)
output.close()
val encoded = bos.toByteArray()
val bis = ByteArrayInputStream(encoded)
val input = HashedBlockInputStream(bis)
val decoded = ByteArrayOutputStream()
while (true) {
val buf = ByteArray(1024)
val read = input.read(buf)
if (read == -1) {
break
}
decoded.write(buf, 0, read)
}
val out = decoded.toByteArray()
assertArrayEquals(orig, out)
}
@Throws(IOException::class)
fun testGZIPStream() {
val testLength = 32000
val orig = ByteArray(testLength)
rand.nextBytes(orig)
val bos = ByteArrayOutputStream()
val hos = HashedBlockOutputStream(bos)
val zos = GZIPOutputStream(hos)
zos.write(orig)
zos.close()
val compressed = bos.toByteArray()
val bis = ByteArrayInputStream(compressed)
val his = HashedBlockInputStream(bis)
val zis = GZIPInputStream(his)
val uncompressed = ByteArray(testLength)
var read = 0
while (read != -1 && testLength - read > 0) {
read += zis.read(uncompressed, read, testLength - read)
}
assertArrayEquals("Output not equal to input", orig, uncompressed)
}
companion object {
private val rand = Random()
}
}

View File

@@ -1,57 +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.tests.utils;
import java.util.Locale;
import com.kunzisoft.keepass.utils.StrUtil;
import junit.framework.TestCase;
public class StrUtilTest extends TestCase {
private final String text = "AbCdEfGhIj";
private final String search = "BcDe";
private final String badSearch = "Ed";
public void testIndexOfIgnoreCase1() {
assertEquals(1, StrUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH));
}
public void testIndexOfIgnoreCase2() {
assertEquals(-1, StrUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH), 2);
}
public void testIndexOfIgnoreCase3() {
assertEquals(-1, StrUtil.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH));
}
private final String repText = "AbCtestingaBc";
private final String repSearch = "ABc";
private final String repSearchBad = "CCCCCC";
private final String repNew = "12345";
private final String repResult = "12345testing12345";
public void testReplaceAllIgnoresCase1() {
assertEquals(repResult, StrUtil.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH));
}
public void testReplaceAllIgnoresCase2() {
assertEquals(repText, StrUtil.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH));
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.utils
import java.util.Locale
import com.kunzisoft.keepass.utils.StringUtil
import junit.framework.TestCase
class StringUtilTest : TestCase() {
private val text = "AbCdEfGhIj"
private val search = "BcDe"
private val badSearch = "Ed"
private val repText = "AbCtestingaBc"
private val repSearch = "ABc"
private val repSearchBad = "CCCCCC"
private val repNew = "12345"
private val repResult = "12345testing12345"
fun testIndexOfIgnoreCase1() {
assertEquals(1, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH))
}
fun testIndexOfIgnoreCase2() {
assertEquals(-1f, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH).toFloat(), 2f)
}
fun testIndexOfIgnoreCase3() {
assertEquals(-1, StringUtil.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH))
}
fun testReplaceAllIgnoresCase1() {
assertEquals(repResult, StringUtil.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH))
}
fun testReplaceAllIgnoresCase2() {
assertEquals(repText, StringUtil.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH))
}
}

View File

@@ -8,9 +8,10 @@
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:label="@string/app_name"
@@ -20,18 +21,21 @@
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"
android:value="" />
<activity
android:name="com.kunzisoft.keepass.fileselect.FileSelectActivity"
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
android:theme="@style/KeepassDXStyle.SplashScreen"
android:label="@string/app_name"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="stateHidden" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -39,12 +43,8 @@
</intent-filter>
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.AboutActivity"
android:launchMode="singleTask"
android:label="@string/menu_about" />
<activity
android:name="com.kunzisoft.keepass.password.PasswordActivity"
android:configChanges="orientation|keyboardHidden"
android:name="com.kunzisoft.keepass.activities.PasswordActivity"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -52,7 +52,7 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="*/*" />
<data android:host="*" />
<data android:pathPattern=".*\\.kdb" />
<data android:pathPattern=".*\\..*\\.kdb" />
@@ -75,36 +75,36 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
</intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/octet-stream"/>
</intent-filter>
</activity>
<!-- Folder picker -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/nnf_provider_paths" />
</provider>
<activity
android:name="com.kunzisoft.keepass.fileselect.FilePickerStylishActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-kdb" />
<data android:mimeType="application/x-kdbx" />
<data android:mimeType="application/x-keepass" />
<data android:host="*" />
<data android:pathPattern=".*" />
<data android:pathPattern=".*\\.*" />
<data android:pathPattern=".*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
</intent-filter>
</activity>
<!-- Main Activity -->
<activity
android:name="com.kunzisoft.keepass.activities.GroupActivity"
android:configChanges="orientation|keyboardHidden"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="adjustPan"
android:launchMode="singleTop">
android:launchMode="singleTask">
<meta-data
android:name="android.app.default_searchable"
android:value="com.kunzisoft.keepass.search.SearchResults"
@@ -119,20 +119,24 @@
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.EntryActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
android:configChanges="keyboardHidden" />
<activity
android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
android:configChanges="keyboardHidden"
android:windowSoftInputMode="adjustResize" />
<!-- About and Settings -->
<activity
android:name="com.kunzisoft.keepass.activities.AboutActivity"
android:launchMode="singleTask"
android:label="@string/menu_about" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity"
android:configChanges="orientation|keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.selection.EntrySelectionAuthActivity"
android:configChanges="orientation|keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
android:configChanges="keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
<activity android:name="com.kunzisoft.keepass.magikeyboard.EntryRetrieverActivity"
android:label="@string/keyboard_name">
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
android:label="@string/keyboard_name"
android:exported="true">
</activity>
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
android:label="@string/keyboard_setting_label">
@@ -141,14 +145,20 @@
</intent-filter>
</activity>
<!-- Receiver for Keyboard -->
<receiver
android:name="com.kunzisoft.keepass.magikeyboard.receiver.NotificationDeleteBroadcastReceiver"
android:exported="false" >
</receiver>
<service
android:name="com.kunzisoft.keepass.notifications.NotificationCopyingService"
android:name="com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService"
android:enabled="true"
android:exported="false" />
<service
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"
android:exported="false" />
<!-- Receiver for Autofill -->
@@ -173,7 +183,10 @@
<action android:name="android.view.InputMethod" />
</intent-filter>
</service>
<service android:name="com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService" />
<service
android:name="com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService"
android:enabled="true"
android:exported="false" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
</application>

Binary file not shown.

Binary file not shown.

View File

@@ -1,2 +0,0 @@
v7<EFBFBD><07>gx<67><78><EFBFBD>"<04>Dm<44>]tIWRP<52>g<18>y<15>/˰1<CBB0><31><13>X <0B><>fW[<5B>F%<25><1E>\<5C>up4
<EFBFBD><EFBFBD>-t;<3B>z<EFBFBD>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<KeyFile>
<Meta>
<Version>1.00</Version>
</Meta>
<Key>
<Data>zaTWphVNtRbspnwkqjy8FGTy5IqCUx9+FNb5H+VdB24=</Data>
</Key>
</KeyFile>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,88 +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;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;
import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.stylish.StylishActivity;
import org.joda.time.DateTime;
public class AboutActivity extends StylishActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.about);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.menu_about));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
String version;
String build;
try {
version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
build = BuildConfig.BUILD_VERSION;
} catch (NameNotFoundException e) {
Log.w(getClass().getSimpleName(), "Unable to get the app or the build version", e);
version = "Unable to get the app version";
build = "Unable to get the build version";
}
version = getString(R.string.version_label, version);
TextView versionTextView = findViewById(R.id.activity_about_version);
versionTextView.setText(version);
build = getString(R.string.build_label, build);
TextView buildTextView = findViewById(R.id.activity_about_build);
buildTextView.setText(build);
TextView disclaimerText = findViewById(R.id.disclaimer);
disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear()));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
switch (id) {
case android.R.id.home:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.pm.PackageManager.NameNotFoundException
import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.MenuItem
import android.widget.TextView
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import org.joda.time.DateTime
class AboutActivity : StylishActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = getString(R.string.menu_about)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
var version: String
var build: String
try {
version = packageManager.getPackageInfo(packageName, 0).versionName
build = BuildConfig.BUILD_VERSION
} catch (e: NameNotFoundException) {
Log.w(javaClass.simpleName, "Unable to get the app or the build version", e)
version = "Unable to get the app version"
build = "Unable to get the build version"
}
version = getString(R.string.version_label, version)
val versionTextView = findViewById<TextView>(R.id.activity_about_version)
versionTextView.text = version
build = getString(R.string.build_label, build)
val buildTextView = findViewById<TextView>(R.id.activity_about_build)
buildTextView.text = build
val disclaimerText = findViewById<TextView>(R.id.disclaimer)
disclaimerText.text = getString(R.string.disclaimer_formal, DateTime().year)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
when (item.itemId) {
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
}

View File

@@ -1,522 +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;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.ExtraFields;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.lock.LockingHideActivity;
import com.kunzisoft.keepass.notifications.NotificationCopyingService;
import com.kunzisoft.keepass.notifications.NotificationField;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.settings.SettingsAutofillActivity;
import com.kunzisoft.keepass.timeout.ClipboardHelper;
import com.kunzisoft.keepass.utils.EmptyUtils;
import com.kunzisoft.keepass.utils.MenuUtil;
import com.kunzisoft.keepass.utils.Types;
import com.kunzisoft.keepass.utils.Util;
import com.kunzisoft.keepass.view.EntryContentsView;
import java.util.ArrayList;
import java.util.Date;
import java.util.UUID;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isClipboardNotificationsEnable;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields;
public class EntryActivity extends LockingHideActivity {
private final static String TAG = EntryActivity.class.getName();
public static final String KEY_ENTRY = "entry";
private ImageView titleIconView;
private TextView titleView;
private EntryContentsView entryContentsView;
private Toolbar toolbar;
protected PwEntry mEntry;
private boolean mShowPassword;
private ClipboardHelper clipboardHelper;
private boolean firstLaunchOfActivity;
private int iconColor;
public static void launch(Activity act, PwEntry pw, boolean readOnly) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, EntryActivity.class);
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_view);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
Database db = App.getDB();
// Likely the app has been killed exit the activity
if ( ! db.getLoaded() ) {
finish();
return;
}
readOnly = db.isReadOnly() || readOnly;
mShowPassword = !PreferencesUtil.isPasswordMask(this);
// Get Entry from UUID
Intent i = getIntent();
UUID uuid = Types.bytestoUUID(i.getByteArrayExtra(KEY_ENTRY));
mEntry = db.getPwDatabase().getEntryByUUIDId(uuid);
if (mEntry == null) {
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show();
finish();
return;
}
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
invalidateOptionsMenu();
// Update last access time.
mEntry.touch(false, false);
// Get views
titleIconView = findViewById(R.id.entry_icon);
titleView = findViewById(R.id.entry_title);
entryContentsView = findViewById(R.id.entry_contents);
entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this));
// Init the clipboard helper
clipboardHelper = new ClipboardHelper(this);
firstLaunchOfActivity = true;
}
@Override
protected void onResume() {
super.onResume();
// Fill data in resume to update from EntryEditActivity
fillData();
invalidateOptionsMenu();
// Start to manage field reference to copy a value from ref
mEntry.startToManageFieldReferences(App.getDB().getPwDatabase());
boolean containsUsernameToCopy =
mEntry.getUsername().length() > 0;
boolean containsPasswordToCopy =
(mEntry.getPassword().length() > 0
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(this));
boolean containsExtraFieldToCopy =
(mEntry.allowExtraFields()
&& ((mEntry.containsCustomFields()
&& mEntry.containsCustomFieldsNotProtected())
|| (mEntry.containsCustomFields()
&& mEntry.containsCustomFieldsProtected()
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(this))
)
);
// If notifications enabled in settings
// Don't if application timeout
if (firstLaunchOfActivity && !App.isShutdown() && isClipboardNotificationsEnable(getApplicationContext())) {
if (containsUsernameToCopy
|| containsPasswordToCopy
|| containsExtraFieldToCopy
) {
// username already copied, waiting for user's action before copy password.
Intent intent = new Intent(this, NotificationCopyingService.class);
intent.setAction(NotificationCopyingService.ACTION_NEW_NOTIFICATION);
if (mEntry.getTitle() != null)
intent.putExtra(NotificationCopyingService.EXTRA_ENTRY_TITLE, mEntry.getTitle());
// Construct notification fields
ArrayList<NotificationField> notificationFields = new ArrayList<>();
// Add username if exists to notifications
if (containsUsernameToCopy)
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.USERNAME,
mEntry.getUsername(),
getResources()));
// Add password to notifications
if (containsPasswordToCopy) {
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.PASSWORD,
mEntry.getPassword(),
getResources()));
}
// Add extra fields
if (containsExtraFieldToCopy) {
try {
mEntry.getFields().doActionToAllCustomProtectedField(new ExtraFields.ActionProtected() {
private int anonymousFieldNumber = 0;
@Override
public void doAction(String key, ProtectedString value) {
//If value is not protected or allowed
if (!value.isProtected() || PreferencesUtil.allowCopyPasswordAndProtectedFields(EntryActivity.this)) {
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.getAnonymousFieldId()[anonymousFieldNumber],
value.toString(),
key,
getResources()));
anonymousFieldNumber++;
}
}
});
} catch (ArrayIndexOutOfBoundsException e) {
Log.w(TAG, "Only " + NotificationField.NotificationFieldId.getAnonymousFieldId().length +
" anonymous notifications are available");
}
}
// Add notifications
intent.putParcelableArrayListExtra(NotificationCopyingService.EXTRA_FIELDS, notificationFields);
startService(intent);
}
mEntry.stopToManageFieldReferences();
}
firstLaunchOfActivity = false;
}
/**
* Check and display learning views
* Displays the explanation for copying a field and editing an entry
*/
private void checkAndPerformedEducation(Menu menu) {
if (PreferencesUtil.isEducationScreensEnabled(this)) {
if (entryContentsView != null && entryContentsView.isUserNamePresent()
&& !PreferencesUtil.isEducationCopyUsernamePerformed(this)) {
TapTargetView.showFor(this,
TapTarget.forView(findViewById(R.id.entry_user_name_action_image),
getString(R.string.education_field_copy_title),
getString(R.string.education_field_copy_summary))
.textColorInt(Color.WHITE)
.tintTarget(false)
.cancelable(true),
new TapTargetView.Listener() {
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view);
clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
getString(R.string.copy_field, getString(R.string.entry_user_name)));
}
@Override
public void onOuterCircleClick(TapTargetView view) {
super.onOuterCircleClick(view);
view.dismiss(false);
// Launch autofill settings
startActivity(new Intent(EntryActivity.this, SettingsAutofillActivity.class));
}
});
PreferencesUtil.saveEducationPreference(this,
R.string.education_copy_username_key);
} else if (!PreferencesUtil.isEducationEntryEditPerformed(this)) {
try {
TapTargetView.showFor(this,
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_edit,
getString(R.string.education_entry_edit_title),
getString(R.string.education_entry_edit_summary))
.textColorInt(Color.WHITE)
.tintTarget(true)
.cancelable(true),
new TapTargetView.Listener() {
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view);
MenuItem editItem = menu.findItem(R.id.menu_edit);
onOptionsItemSelected(editItem);
}
@Override
public void onOuterCircleClick(TapTargetView view) {
super.onOuterCircleClick(view);
view.dismiss(false);
// Open Keepass doc to create field references
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.field_references_url)));
startActivity(browserIntent);
}
});
PreferencesUtil.saveEducationPreference(this,
R.string.education_entry_edit_key);
} catch (Exception e) {
// If icon not visible
Log.w(TAG, "Can't performed education for entry's edition");
}
}
}
}
protected void fillData() {
Database db = App.getDB();
PwDatabase pm = db.getPwDatabase();
mEntry.startToManageFieldReferences(pm);
// Assign title icon
db.getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), iconColor);
// Assign title text
titleView.setText(mEntry.getVisualTitle());
// Assign basic fields
entryContentsView.assignUserName(mEntry.getUsername());
entryContentsView.assignUserNameCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
getString(R.string.copy_field, getString(R.string.entry_user_name)))
);
boolean allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this);
entryContentsView.assignPassword(mEntry.getPassword(), allowCopyPassword);
if (allowCopyPassword) {
entryContentsView.assignPasswordCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
getString(R.string.copy_field, getString(R.string.entry_password)))
);
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
entryContentsView.assignPasswordCopyListener(v -> {
String message = getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning);
AlertDialog warningDialog = new AlertDialog.Builder(EntryActivity.this)
.setMessage(message).create();
warningDialog.setButton(AlertDialog.BUTTON1, getText(android.R.string.ok),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, true);
dialog.dismiss();
fillData();
});
warningDialog.setButton(AlertDialog.BUTTON2, getText(android.R.string.cancel),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, false);
dialog.dismiss();
fillData();
});
warningDialog.show();
});
} else {
entryContentsView.assignPasswordCopyListener(null);
}
}
entryContentsView.assignURL(mEntry.getUrl());
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
entryContentsView.assignComment(mEntry.getNotes());
// Assign custom fields
if (mEntry.allowExtraFields()) {
entryContentsView.clearExtraFields();
mEntry.getFields().doActionToAllCustomProtectedField((label, value) -> {
boolean showAction = (!value.isProtected() || PreferencesUtil.allowCopyPasswordAndProtectedFields(EntryActivity.this));
entryContentsView.addExtraField(label, value, showAction, view ->
clipboardHelper.timeoutCopyToClipboard(
value.toString(),
getString(R.string.copy_field, label)
)
);
});
}
// Assign dates
entryContentsView.assignCreationDate(mEntry.getCreationTime().getDate());
entryContentsView.assignModificationDate(mEntry.getLastModificationTime().getDate());
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime().getDate());
Date expires = mEntry.getExpiryTime().getDate();
if ( mEntry.isExpires() ) {
entryContentsView.assignExpiresDate(expires);
} else {
entryContentsView.assignExpiresDate(getString(R.string.never));
}
mEntry.stopToManageFieldReferences();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
fillData();
break;
}
}
private void changeShowPasswordIcon(MenuItem togglePassword) {
if ( mShowPassword ) {
togglePassword.setTitle(R.string.menu_hide_password);
togglePassword.setIcon(R.drawable.ic_visibility_off_white_24dp);
} else {
togglePassword.setTitle(R.string.menu_showpass);
togglePassword.setIcon(R.drawable.ic_visibility_white_24dp);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.entry, menu);
inflater.inflate(R.menu.database_lock, menu);
if (readOnly) {
MenuItem edit = menu.findItem(R.id.menu_edit);
if (edit != null)
edit.setVisible(false);
}
MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass);
if (entryContentsView != null && togglePassword != null) {
if (entryContentsView.isPasswordPresent() || entryContentsView.atLeastOneFieldProtectedPresent()) {
changeShowPasswordIcon(togglePassword);
} else {
togglePassword.setVisible(false);
}
}
MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url);
if (gotoUrl != null) {
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so mEntry may not be set
if (mEntry == null) {
gotoUrl.setVisible(false);
} else {
String url = mEntry.getUrl();
if (EmptyUtils.isNullOrEmpty(url)) {
// disable button if url is not available
gotoUrl.setVisible(false);
}
}
}
// Show education views
new Handler().post(() -> checkAndPerformedEducation(menu));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_contribute:
return MenuUtil.onContributionItemSelected(this);
case R.id.menu_toggle_pass:
mShowPassword = !mShowPassword;
changeShowPasswordIcon(item);
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
return true;
case R.id.menu_edit:
EntryEditActivity.launch(EntryActivity.this, mEntry);
return true;
case R.id.menu_goto_url:
String url;
url = mEntry.getUrl();
// Default http:// if no protocol specified
if ( ! url.contains("://") ) {
url = "http://" + url;
}
try {
Util.gotoUrl(this, url);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show();
}
return true;
case R.id.menu_lock:
lockAndExit();
return true;
case android.R.id.home :
finish(); // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item);
}
@Override
public void finish() {
// Transit data in previous Activity after an update
/*
TODO Slowdown when add entry as result
Intent intent = new Intent();
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish();
}
}

View File

@@ -0,0 +1,564 @@
/*
* 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.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
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
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.LockingActivity
import com.kunzisoft.keepass.database.element.Database
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 : LockingActivity() {
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var titleIconView: ImageView? = null
private var historyView: View? = null
private var entryContentsView: EntryContentsView? = null
private var entryProgress: ProgressBar? = null
private var toolbar: Toolbar? = null
private var mDatabase: Database? = 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
private var iconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_entry)
toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
mDatabase = Database.getInstance()
mReadOnly = mDatabase!!.isReadOnly || mReadOnly
mShowPassword = !PreferencesUtil.isPasswordMask(this)
// Retrieve the textColor to tint the icon
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
iconColor = taIconColor.getColor(0, Color.BLACK)
taIconColor.recycle()
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
invalidateOptionsMenu()
// Get views
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container)
entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryProgress = findViewById(R.id.entry_progress)
// 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() {
super.onResume()
// Get Entry from UUID
try {
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, mEntryHistoryPosition)
mEntryHistoryPosition = historyPosition
if (historyPosition >= 0) {
mIsHistory = true
mEntry = mEntry?.getHistory()?.get(historyPosition)
}
if (mEntry == null) {
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
finish()
return
}
// Update last access time.
mEntry?.touch(modified = false, touchParents = false)
mEntry?.let { entry ->
// Fill data in resume to update from EntryEditActivity
fillEntryDataInContentsView(entry)
// Refresh Menu
invalidateOptionsMenu()
val entryInfo = entry.getEntryInfo(Database.getInstance())
// Manage entry copy to start notification if allowed
if (firstLaunchOfActivity) {
// Manage entry to launch copying notification if allowed
ClipboardEntryNotificationService.launchNotificationIfAllowed(this, entryInfo)
// Manage entry to populate Magikeyboard and launch keyboard notification if allowed
if (PreferencesUtil.isKeyboardEntrySelectionEnable(this)) {
MagikIME.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
}
}
}
mAttachmentFileBinderManager?.apply {
registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
entryContentsView?.updateAttachmentDownloadProgress(attachment)
}
}
}
firstLaunchOfActivity = false
}
override fun onPause() {
mAttachmentFileBinderManager?.unregisterProgressTask()
super.onPause()
}
private fun fillEntryDataInContentsView(entry: Entry) {
val database = Database.getInstance()
database.startManageEntry(entry)
// Assign title icon
titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor)
// Assign title text
val entryTitle = entry.title
collapsingToolbarLayout?.title = entryTitle
toolbar?.title = entryTitle
// Assign basic fields
entryContentsView?.assignUserName(entry.username)
entryContentsView?.assignUserNameCopyListener(View.OnClickListener {
database.startManageEntry(entry)
clipboardHelper?.timeoutCopyToClipboard(entry.username,
getString(R.string.copy_field,
getString(R.string.entry_user_name)))
database.stopManageEntry(entry)
})
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)
val allowCopyPasswordAndProtectedFields =
PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
val showWarningClipboardDialogOnClickListener = View.OnClickListener {
AlertDialog.Builder(this@EntryActivity)
.setMessage(getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning))
.create().apply {
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
show()
}
}
entryContentsView?.assignPassword(entry.password, allowCopyPasswordAndProtectedFields)
if (allowCopyPasswordAndProtectedFields) {
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
database.startManageEntry(entry)
clipboardHelper?.timeoutCopyToClipboard(entry.password,
getString(R.string.copy_field,
getString(R.string.entry_password)))
database.stopManageEntry(entry)
})
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
entryContentsView?.assignPasswordCopyListener(showWarningClipboardDialogOnClickListener)
} else {
entryContentsView?.assignPasswordCopyListener(null)
}
}
//Assign OTP field
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
View.OnClickListener {
entry.getOtpElement()?.let { otpElement ->
clipboardHelper?.timeoutCopyToClipboard(
otpElement.token,
getString(R.string.copy_field, getString(R.string.entry_otp))
)
}
})
entryContentsView?.assignURL(entry.url)
entryContentsView?.assignComment(entry.notes)
// Assign custom fields
if (entry.allowCustomFields()) {
entryContentsView?.clearExtraFields()
for (element in entry.customFields.entries) {
val label = element.key
val value = element.value
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
if (allowCopyProtectedField) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(
value.toString(),
getString(R.string.copy_field, label)
)
})
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
} else {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
}
}
}
}
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)
entryContentsView?.assignLastAccessDate(entry.lastAccessTime)
entryContentsView?.setExpires(entry.isCurrentlyExpires)
if (entry.expires) {
entryContentsView?.assignExpiresDate(entry.expiryTime)
} else {
entryContentsView?.assignExpiresDate(getString(R.string.never))
}
// Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
if (mIsHistory) {
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
taColorAccent.recycle()
}
val entryHistory = entry.getHistory()
// TODO isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) {
entryContentsView?.assignHistory(entryHistory)
entryContentsView?.onHistoryClick { historyItem, 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
mEntry?.let {
fillEntryDataInContentsView(it)
}
}
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
}
}
}
}
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
if (mShowPassword) {
togglePassword?.setTitle(R.string.menu_hide_password)
togglePassword?.setIcon(R.drawable.ic_visibility_off_white_24dp)
} else {
togglePassword?.setTitle(R.string.menu_showpass)
togglePassword?.setIcon(R.drawable.ic_visibility_white_24dp)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
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
}
val togglePassword = menu.findItem(R.id.menu_toggle_pass)
entryContentsView?.let {
if (it.isPasswordPresent || it.atLeastOneFieldProtectedPresent()) {
changeShowPasswordIcon(togglePassword)
} else {
togglePassword?.isVisible = false
}
}
val gotoUrl = menu.findItem(R.id.menu_goto_url)
gotoUrl?.apply {
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so mEntry may not be set
if (mEntry == null) {
isVisible = false
} else {
if (mEntry?.url?.isEmpty() != false) {
// disable button if url is not available
isVisible = false
}
}
}
// Show education views
Handler().post { performedNextEducation(EntryActivityEducation(this), menu) }
return true
}
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
menu: Menu) {
val entryCopyEducationPerformed = entryContentsView?.isUserNamePresent == true
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
findViewById(R.id.entry_user_name_action_image),
{
clipboardHelper?.timeoutCopyToClipboard(mEntry!!.username,
getString(R.string.copy_field,
getString(R.string.entry_user_name)))
},
{
// Launch autofill settings
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
})
if (!entryCopyEducationPerformed) {
// entryEditEducationPerformed
toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
toolbar!!.findViewById(R.id.menu_edit),
{
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
},
{
// Open Keepass doc to create field references
startActivity(Intent(Intent.ACTION_VIEW,
UriUtil.parse(getString(R.string.field_references_url))))
})
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_contribute -> {
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 ?: ""
// Default http:// if no protocol specified
if (!url.contains("://")) {
url = "http://$url"
}
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)
}
override fun finish() {
// Transit data in previous Activity after an update
/*
TODO Slowdown when add entry as result
Intent intent = new Intent();
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
onFinish(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish()
}
companion object {
private val TAG = EntryActivity::class.java.name
const val KEY_ENTRY = "KEY_ENTRY"
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
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)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
if (historyPosition != null)
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
}
}
}
}

View File

@@ -1,590 +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;
import android.app.Activity;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwDate;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.action.RunnableOnFinish;
import com.kunzisoft.keepass.database.action.node.AddEntryRunnable;
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.lock.LockingHideActivity;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
import com.kunzisoft.keepass.utils.MenuUtil;
import com.kunzisoft.keepass.utils.Types;
import com.kunzisoft.keepass.utils.Util;
import com.kunzisoft.keepass.view.EntryEditCustomField;
import java.util.UUID;
import javax.annotation.Nullable;
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD;
public class EntryEditActivity extends LockingHideActivity
implements IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener {
private static final String TAG = EntryEditActivity.class.getName();
// Keys for current Activity
public static final String KEY_ENTRY = "entry";
public static final String KEY_PARENT = "parent";
// Keys for callback
public static final int ADD_ENTRY_RESULT_CODE = 31;
public static final int UPDATE_ENTRY_RESULT_CODE = 32;
public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
private Database database;
protected PwEntry mEntry;
protected PwEntry mCallbackNewEntry;
protected boolean mIsNew;
protected PwIconStandard mSelectedIconStandard;
// Views
private ScrollView scrollView;
private EditText entryTitleView;
private ImageView entryIconView;
private EditText entryUserNameView;
private EditText entryUrlView;
private EditText entryPasswordView;
private EditText entryConfirmationPasswordView;
private View generatePasswordView;
private EditText entryCommentView;
private ViewGroup entryExtraFieldsContainer;
private View addNewFieldView;
private View saveView;
private int iconColor;
/**
* Launch EntryEditActivity to update an existing entry
*
* @param act from activity
* @param pw Entry to update
*/
public static void launch(Activity act, PwEntry pw) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, EntryEditActivity.class);
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
}
/**
* Launch EntryEditActivity to add a new entry
*
* @param act from activity
* @param pwGroup Group who will contains new entry
*/
public static void launch(Activity act, PwGroup pwGroup) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, EntryEditActivity.class);
intent.putExtra(KEY_PARENT, pwGroup.getId());
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_edit);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
scrollView = findViewById(R.id.entry_scroll);
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
entryTitleView = findViewById(R.id.entry_title);
entryIconView = findViewById(R.id.icon_button);
entryUserNameView = findViewById(R.id.entry_user_name);
entryUrlView = findViewById(R.id.entry_url);
entryPasswordView = findViewById(R.id.entry_password);
entryConfirmationPasswordView = findViewById(R.id.entry_confpassword);
entryCommentView = findViewById(R.id.entry_comment);
entryExtraFieldsContainer = findViewById(R.id.advanced_container);
// Likely the app has been killed exit the activity
database = App.getDB();
if ( ! database.getLoaded() ) {
finish();
return;
}
Intent intent = getIntent();
byte[] uuidBytes = intent.getByteArrayExtra(KEY_ENTRY);
// Retrieve the textColor to tint the icon
int[] attrs = {android.R.attr.textColorPrimary};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
mSelectedIconStandard = database.getPwDatabase().getIconFactory().getUnknownIcon();
PwDatabase pm = database.getPwDatabase();
if ( uuidBytes == null ) {
PwGroupId parentId = intent.getParcelableExtra(KEY_PARENT);
PwGroup parent = pm.getGroupByGroupId(parentId);
mEntry = database.createEntry(parent);
mIsNew = true;
// Add the default icon
database.getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, iconColor);
} else {
UUID uuid = Types.bytestoUUID(uuidBytes);
mEntry = pm.getEntryByUUIDId(uuid);
mIsNew = false;
fillData();
}
// Assign title
setTitle((mIsNew) ? getString(R.string.add_entry) : getString(R.string.edit_entry));
// Retrieve the icon after an orientation change
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ICON_STANDARD)) {
iconPicked(savedInstanceState);
}
// Add listener to the icon
entryIconView.setOnClickListener(v ->
IconPickerDialogFragment.launch(EntryEditActivity.this));
// Generate password button
generatePasswordView = findViewById(R.id.generate_button);
generatePasswordView.setOnClickListener(v -> openPasswordGenerator());
// Save button
saveView = findViewById(R.id.entry_save);
saveView.setOnClickListener(v -> saveEntry());
if (mEntry.allowExtraFields()) {
addNewFieldView = findViewById(R.id.add_new_field);
addNewFieldView.setVisibility(View.VISIBLE);
addNewFieldView.setOnClickListener(v -> addNewCustomField());
}
// Verify the education views
checkAndPerformedEducation();
}
/**
* Open the password generator fragment
*/
private void openPasswordGenerator() {
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
}
/**
* Add a new view to fill in the information of the customized field
*/
private void addNewCustomField() {
EntryEditCustomField entryEditCustomField = new EntryEditCustomField(EntryEditActivity.this);
entryEditCustomField.setData("", new ProtectedString(false, ""));
boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
entryEditCustomField.setFontVisibility(visibilityFontActivated);
entryExtraFieldsContainer.addView(entryEditCustomField);
// Scroll bottom
scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
}
/**
* Saves the new entry or update an existing entry in the database
*/
private void saveEntry() {
if (!validateBeforeSaving()) {
return;
}
mCallbackNewEntry = populateNewEntry();
// Open a progress dialog and save entry
AfterActionNodeOnFinish onFinish = new AfterSave();
EntryEditActivity act = EntryEditActivity.this;
RunnableOnFinish task;
if ( mIsNew ) {
task = new AddEntryRunnable(act, database, mCallbackNewEntry, onFinish);
} else {
task = new UpdateEntryRunnable(act, database, mEntry, mCallbackNewEntry, onFinish);
}
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
SaveDatabaseProgressTaskDialogFragment.start(
getSupportFragmentManager())
));
new Thread(task).start();
}
/**
* Check and display learning views
* Displays the explanation for the icon selection, the password generator and for a new field
*/
private void checkAndPerformedEducation() {
if (PreferencesUtil.isEducationScreensEnabled(this)) {
// TODO Show icon
if (!PreferencesUtil.isEducationPasswordGeneratorPerformed(this)) {
TapTargetView.showFor(this,
TapTarget.forView(generatePasswordView,
getString(R.string.education_generate_password_title),
getString(R.string.education_generate_password_summary))
.textColorInt(Color.WHITE)
.tintTarget(false)
.cancelable(true),
new TapTargetView.Listener() {
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view);
openPasswordGenerator();
}
@Override
public void onOuterCircleClick(TapTargetView view) {
super.onOuterCircleClick(view);
view.dismiss(false);
}
});
PreferencesUtil.saveEducationPreference(this,
R.string.education_password_generator_key);
} else if (mEntry.allowExtraFields()
&& !mEntry.containsCustomFields()
&& !PreferencesUtil.isEducationEntryNewFieldPerformed(this)) {
TapTargetView.showFor(this,
TapTarget.forView(addNewFieldView,
getString(R.string.education_entry_new_field_title),
getString(R.string.education_entry_new_field_summary))
.textColorInt(Color.WHITE)
.tintTarget(false)
.cancelable(true),
new TapTargetView.Listener() {
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view);
addNewCustomField();
}
@Override
public void onOuterCircleClick(TapTargetView view) {
super.onOuterCircleClick(view);
view.dismiss(false);
}
});
PreferencesUtil.saveEducationPreference(this,
R.string.education_entry_new_field_key);
}
}
}
/**
* Utility class to retrieve a validation or an error with a message
*/
private class ErrorValidation {
static final int unknownMessage = -1;
boolean isValidate = false;
int messageId = unknownMessage;
void showValidationErrorIfNeeded() {
if (!isValidate && messageId != unknownMessage)
Toast.makeText(EntryEditActivity.this, messageId, Toast.LENGTH_LONG).show();
}
}
/**
* Validate or not the entry form
*
* @return ErrorValidation An error with a message or a validation without message
*/
protected ErrorValidation validate() {
ErrorValidation errorValidation = new ErrorValidation();
// Require title
String title = entryTitleView.getText().toString();
if ( title.length() == 0 ) {
errorValidation.messageId = R.string.error_title_required;
return errorValidation;
}
// Validate password
String pass = entryPasswordView.getText().toString();
String conf = entryConfirmationPasswordView.getText().toString();
if ( ! pass.equals(conf) ) {
errorValidation.messageId = R.string.error_pass_match;
return errorValidation;
}
// Validate extra fields
if (mEntry.allowExtraFields()) {
for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
EntryEditCustomField entryEditCustomField = (EntryEditCustomField) entryExtraFieldsContainer.getChildAt(i);
String key = entryEditCustomField.getLabel();
if (key == null || key.length() == 0) {
errorValidation.messageId = R.string.error_string_key;
return errorValidation;
}
}
}
errorValidation.isValidate = true;
return errorValidation;
}
/**
* Launch a validation with {@link #validate()} and show the error if present
*
* @return true if the form was validate or false if not
*/
protected boolean validateBeforeSaving() {
ErrorValidation errorValidation = validate();
errorValidation.showValidationErrorIfNeeded();
return errorValidation.isValidate;
}
protected PwEntry populateNewEntry() {
PwDatabase db = App.getDB().getPwDatabase();
PwEntry newEntry = mEntry.clone();
newEntry.startToManageFieldReferences(db);
newEntry.createBackup(db);
newEntry.setLastAccessTime(new PwDate());
newEntry.setLastModificationTime(new PwDate());
newEntry.setTitle(entryTitleView.getText().toString());
newEntry.setIconStandard(retrieveIcon());
newEntry.setUrl(entryUrlView.getText().toString());
newEntry.setUsername(entryUserNameView.getText().toString());
newEntry.setNotes(entryCommentView.getText().toString());
newEntry.setPassword(entryPasswordView.getText().toString());
if (newEntry.allowExtraFields()) {
// Delete all extra strings
newEntry.removeAllCustomFields();
// Add extra fields from views
for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
EntryEditCustomField view = (EntryEditCustomField) entryExtraFieldsContainer.getChildAt(i);
String key = view.getLabel();
String value = view.getValue();
boolean protect = view.isProtected();
newEntry.addExtraField(key, new ProtectedString(protect, value));
}
}
newEntry.stopToManageFieldReferences();
return newEntry;
}
/**
* Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
*/
private PwIconStandard retrieveIcon() {
if (!mSelectedIconStandard.isUnknown())
return mSelectedIconStandard;
else {
if (mIsNew) {
return database.getPwDatabase().getIconFactory().getKeyIcon();
}
else {
// Keep previous icon, if no new one was selected
return mEntry.getIconStandard();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_contribute:
return MenuUtil.onContributionItemSelected(this);
case android.R.id.home:
finish();
}
return super.onOptionsItemSelected(item);
}
private void assignIconView() {
database.getDrawFactory()
.assignDatabaseIconTo(
this,
entryIconView,
mEntry.getIcon(),
iconColor);
}
protected void fillData() {
assignIconView();
// Don't start the field reference manager, we want to see the raw ref
mEntry.stopToManageFieldReferences();
entryTitleView.setText(mEntry.getTitle());
entryUserNameView.setText(mEntry.getUsername());
entryUrlView.setText(mEntry.getUrl());
String password = mEntry.getPassword();
entryPasswordView.setText(password);
entryConfirmationPasswordView.setText(password);
entryCommentView.setText(mEntry.getNotes());
boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
if (visibilityFontActivated) {
Util.applyFontVisibilityTo(this, entryUserNameView);
Util.applyFontVisibilityTo(this, entryPasswordView);
Util.applyFontVisibilityTo(this, entryConfirmationPasswordView);
Util.applyFontVisibilityTo(this, entryCommentView);
}
if (mEntry.allowExtraFields()) {
LinearLayout container = findViewById(R.id.advanced_container);
mEntry.getFields().doActionToAllCustomProtectedField((key, value) -> {
EntryEditCustomField entryEditCustomField = new EntryEditCustomField(EntryEditActivity.this);
entryEditCustomField.setData(key, value);
entryEditCustomField.setFontVisibility(visibilityFontActivated);
container.addView(entryEditCustomField);
});
}
}
@Override
public void iconPicked(Bundle bundle) {
mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD);
mEntry.setIconStandard(mSelectedIconStandard);
assignIconView();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (!mSelectedIconStandard.isUnknown()) {
outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard);
super.onSaveInstanceState(outState);
}
}
@Override
public void acceptPassword(Bundle bundle) {
String generatedPassword = bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID);
entryPasswordView.setText(generatedPassword);
entryConfirmationPasswordView.setText(generatedPassword);
checkAndPerformedEducation();
}
@Override
public void cancelPassword(Bundle bundle) {
// Do nothing here
}
@Override
public void finish() {
// Assign entry callback as a result in all case
try {
if (mCallbackNewEntry != null) {
Bundle bundle = new Bundle();
Intent intentEntry = new Intent();
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
intentEntry.putExtras(bundle);
if (mIsNew) {
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
} else {
setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry);
}
}
super.finish();
} catch (Exception e) {
// Exception when parcelable can't be done
Log.e(TAG, "Cant add entry as result", e);
}
}
private final class AfterSave extends AfterActionNodeOnFinish {
@Override
public void run(@Nullable PwNode oldNode, @Nullable PwNode newNode) {
runOnUiThread(() -> {
if ( mSuccess ) {
finish();
} else {
displayMessage(EntryEditActivity.this);
}
SaveDatabaseProgressTaskDialogFragment.stop(EntryEditActivity.this);
});
}
}
}

View File

@@ -0,0 +1,481 @@
/*
* 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.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.Menu
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.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
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.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 : LockingActivity(),
IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener {
private var mDatabase: Database? = null
// Refs of an entry and group in database, are not modifiable
private var mEntry: Entry? = null
private var mParent: Group? = null
// New or copy of mEntry in the database to be modifiable
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
// Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_entry_edit)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp)
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
entryEditContentsView = findViewById(R.id.entry_edit_contents)
entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
// Likely the app has been killed exit the activity
mDatabase = Database.getInstance()
// Entry is retrieve, it's an entry to update
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
mIsNew = false
// Create an Entry copy to modify from the database entry
mEntry = mDatabase?.getEntryById(it)
// Retrieve the parent
mEntry?.let { entry ->
mParent = entry.parent
// If no parent, add root group as parent
if (mParent == null) {
mParent = mDatabase?.rootGroup
entry.parent = mParent
}
}
// Create the new entry from the current one
if (savedInstanceState == null
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
mEntry?.let { entry ->
// Create a copy to modify
mNewEntry = Entry(entry).also { newEntry ->
// WARNING Remove the parent to keep memory with parcelable
newEntry.removeParent()
}
}
}
}
// Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
mIsNew = true
// Create an empty new entry
if (savedInstanceState == null
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
mNewEntry = mDatabase?.createEntry()
}
mParent = mDatabase?.getGroupById(it)
// Add the default icon
mDatabase?.drawFactory?.let { iconFactory ->
entryEditContentsView?.setDefaultIcon(iconFactory)
}
}
// Retrieve the new entry after an orientation change
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY)
}
// Close the activity if entry or parent can't be retrieve
if (mNewEntry == null || mParent == null) {
finish()
return
}
populateViewsWithEntry(mNewEntry!!)
// Assign title
title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry)
// Add listener to the icon
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
// Generate password button
entryEditContentsView?.setOnPasswordGeneratorClickListener { openPasswordGenerator() }
// Save button
saveView = findViewById(R.id.entry_edit_save)
saveView?.setOnClickListener { saveEntry() }
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) {
addNewCustomField()
}
// Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this)
// Create progress dialog
mProgressDialogThread?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
if (result.isSuccess)
finish()
}
}
// Show error
if (!result.isSuccess) {
result.message?.let { resultMessage ->
Snackbar.make(coordinatorLayout!!,
resultMessage,
Snackbar.LENGTH_LONG).asError().show()
}
}
}
}
private fun populateViewsWithEntry(newEntry: Entry) {
// Don't start the field reference manager, we want to see the raw ref
mDatabase?.stopManageEntry(newEntry)
// Set info in temp parameters
temporarilySaveAndShowSelectedIcon(newEntry.icon)
// Set info in view
entryEditContentsView?.apply {
title = newEntry.title
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
url = newEntry.url
password = newEntry.password
notes = newEntry.notes
for (entry in newEntry.customFields.entries) {
post {
putCustomField(entry.key, entry.value)
}
}
}
}
private fun populateEntryWithViews(newEntry: Entry) {
mDatabase?.startManageEntry(newEntry)
newEntry.apply {
// Build info from view
entryEditContentsView?.let { entryView ->
removeAllFields()
title = entryView.title
username = entryView.username
url = entryView.url
password = entryView.password
notes = entryView.notes
entryView.customFields.forEach { customField ->
putExtraField(customField.name, customField.protectedValue)
}
}
}
mDatabase?.stopManageEntry(newEntry)
}
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
mNewEntry?.icon = icon
mDatabase?.drawFactory?.let { iconDrawFactory ->
entryEditContentsView?.setIcon(iconDrawFactory, icon)
}
}
/**
* Open the password generator fragment
*/
private fun openPasswordGenerator() {
GeneratePasswordDialogFragment().show(supportFragmentManager, "PasswordGeneratorFragment")
}
/**
* Add a new customized field view and scroll to bottom
*/
private fun addNewCustomField() {
entryEditContentsView?.addEmptyCustomField()
}
/**
* Saves the new entry or update an existing entry in the database
*/
private fun saveEntry() {
// Launch a validation and show the error if present
if (entryEditContentsView?.isValid() == true) {
// Clone the entry
mNewEntry?.let { newEntry ->
// WARNING Add the parent previously deleted
newEntry.parent = mEntry?.parent
// Build info
newEntry.lastAccessTime = DateInstant()
newEntry.lastModificationTime = DateInstant()
populateEntryWithViews(newEntry)
// Open a progress dialog and save entry
if (mIsNew) {
mParent?.let { parent ->
mProgressDialogThread?.startDatabaseCreateEntry(
newEntry,
parent,
!mReadOnly && mAutoSaveEnable
)
}
} else {
mEntry?.let { oldEntry ->
mProgressDialogThread?.startDatabaseUpdateEntry(
oldEntry,
newEntry,
!mReadOnly && mAutoSaveEnable
)
}
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.database, menu)
// Save database not needed here
menu.findItem(R.id.menu_save_database)?.isVisible = false
MenuUtil.contributionMenuInflater(inflater, menu)
if (mDatabase?.allowOTP == true)
inflater.inflate(R.menu.entry_otp, menu)
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
}
return true
}
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
val passwordView = entryEditContentsView?.generatePasswordView
val addNewFieldView = entryEditContentsView?.addNewFieldButton
val generatePasswordEducationPerformed = passwordView != null
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
passwordView,
{
openPasswordGenerator()
},
{
performedNextEducation(entryEditActivityEducation)
}
)
if (!generatePasswordEducationPerformed) {
// entryNewFieldEducationPerformed
mNewEntry != null && mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView,
{
addNewCustomField()
})
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_lock -> {
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
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
.show(supportFragmentManager, "addOTPDialog")
return true
}
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
override fun onOtpCreated(otpElement: OtpElement) {
// Update the otp field with otpauth:// url
val otpField = OtpEntryFields.buildOtpField(otpElement,
mEntry?.title, mEntry?.username)
entryEditContentsView?.putCustomField(otpField.name, otpField.protectedValue)
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
}
override fun iconPicked(bundle: Bundle) {
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
temporarilySaveAndShowSelectedIcon(icon)
}
}
override fun onSaveInstanceState(outState: Bundle) {
mNewEntry?.let {
populateEntryWithViews(it)
outState.putParcelable(KEY_NEW_ENTRY, it)
}
super.onSaveInstanceState(outState)
}
override fun acceptPassword(bundle: Bundle) {
bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let {
entryEditContentsView?.password = it
}
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
}
}
override fun cancelPassword(bundle: Bundle) {
// Do nothing here
}
override fun finish() {
// Assign entry callback as a result in all case
try {
mNewEntry?.let {
val bundle = Bundle()
val intentEntry = Intent()
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry)
intentEntry.putExtras(bundle)
if (mIsNew) {
setResult(ADD_ENTRY_RESULT_CODE, intentEntry)
} else {
setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry)
}
}
super.finish()
} catch (e: Exception) {
// Exception when parcelable can't be done
Log.e(TAG, "Cant add entry as result", e)
}
}
companion object {
private val TAG = EntryEditActivity::class.java.name
// Keys for current Activity
const val KEY_ENTRY = "entry"
const val KEY_PARENT = "parent"
// SaveInstanceState
const val KEY_NEW_ENTRY = "new_entry"
// Keys for callback
const val ADD_ENTRY_RESULT_CODE = 31
const val UPDATE_ENTRY_RESULT_CODE = 32
const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
/**
* Launch EntryEditActivity to update an existing entry
*
* @param activity from activity
* @param pwEntry Entry to update
*/
fun launch(activity: Activity, pwEntry: Entry) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
}
}
/**
* Launch EntryEditActivity to add a new entry
*
* @param activity from activity
* @param pwGroup Group who will contains new entry
*/
fun launch(activity: Activity, pwGroup: Group) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, pwGroup.nodeId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
}
}
}
}

View File

@@ -0,0 +1,444 @@
/*
* 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.annotation.SuppressLint
import android.app.Activity
import android.app.assist.AssistStructure
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.preference.PreferenceManager
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillHelper
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.*
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.*
import java.io.FileNotFoundException
class FileDatabaseSelectActivity : StylishActivity(),
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
// Views
private var fileListContainer: View? = null
private var createButtonView: View? = null
private var openDatabaseButtonView: View? = null
// Adapter to manage database history list
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
private var mFileDatabaseHistoryAction: FileDatabaseHistoryAction? = null
private var mDatabaseFileUri: Uri? = null
private var mOpenFileHelper: OpenFileHelper? = null
private var mProgressDialogThread: ProgressDialogThread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection)
fileListContainer = findViewById(R.id.container_file_list)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = ""
setSupportActionBar(toolbar)
// Create button
createButtonView = findViewById(R.id.create_database_button)
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() }
mOpenFileHelper = OpenFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_database_button)
openDatabaseButtonView?.setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
// History list
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
fileDatabaseHistoryRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
// Removes blinks
(fileDatabaseHistoryRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
// Construct adapter with listeners
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this)
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
UriUtil.parse(fileDatabaseHistoryEntityToOpen.databaseUri)?.let { databaseFileUri ->
launchPasswordActivity(
databaseFileUri,
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
}
updateFileListVisibility()
}
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
// Remove from app database
mFileDatabaseHistoryAction?.deleteFileDatabaseHistory(fileDatabaseHistoryToDelete) { fileHistoryDeleted ->
// Remove from adapter
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}
}
true
}
mAdapterDatabaseHistory?.setOnSaveAliasListener { fileDatabaseHistoryWithNewAlias ->
mFileDatabaseHistoryAction?.addOrUpdateFileDatabaseHistory(fileDatabaseHistoryWithNewAlias)
}
fileDatabaseHistoryRecyclerView.adapter = mAdapterDatabaseHistory
// Load default database if not an orientation change
if (!(savedInstanceState != null
&& savedInstanceState.containsKey(EXTRA_STAY)
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val databasePath = prefs.getString(PasswordActivity.KEY_DEFAULT_DATABASE_PATH, "")
UriUtil.parse(databasePath)?.let { databaseFileUri ->
launchPasswordActivityWithPath(databaseFileUri)
} ?: run {
Log.i(TAG, "Unable to launch Password Activity")
}
}
// Retrieve the database URI provided by file manager after an orientation change
if (savedInstanceState != null
&& savedInstanceState.containsKey(EXTRA_DATABASE_URI)) {
mDatabaseFileUri = savedInstanceState.getParcelable(EXTRA_DATABASE_URI)
}
// Attach the dialog thread to this activity
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, _ ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
// TODO Check
// mAdapterDatabaseHistory?.notifyDataSetChanged()
// updateFileListVisibility()
GroupActivity.launch(this@FileDatabaseSelectActivity)
}
}
}
}
}
/**
* Create a new file by calling the content provider
*/
@SuppressLint("InlinedApi")
private fun createNewFile() {
createDocument(this, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass")
}
private fun fileNoFoundAction(e: FileNotFoundException) {
val error = getString(R.string.file_not_found_content)
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error, e)
}
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
EntrySelectionHelper.doEntrySelectionAction(intent,
{
try {
PasswordActivity.launch(this@FileDatabaseSelectActivity,
databaseUri, keyFile)
} catch (e: FileNotFoundException) {
fileNoFoundAction(e)
}
},
{
try {
PasswordActivity.launchForKeyboardResult(this@FileDatabaseSelectActivity,
databaseUri, keyFile)
finish()
} catch (e: FileNotFoundException) {
fileNoFoundAction(e)
}
},
{ assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
databaseUri, keyFile,
assistStructure)
} catch (e: FileNotFoundException) {
fileNoFoundAction(e)
}
}
})
}
private fun launchGroupActivity(readOnly: Boolean) {
EntrySelectionHelper.doEntrySelectionAction(intent,
{
GroupActivity.launch(this@FileDatabaseSelectActivity, readOnly)
},
{
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity, readOnly)
// Do not keep history
finish()
},
{ assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity, assistStructure, readOnly)
}
})
}
private fun launchPasswordActivityWithPath(databaseUri: Uri) {
launchPasswordActivity(databaseUri, null)
// Delete flickering for kitkat <=
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
overridePendingTransition(0, 0)
}
private fun updateExternalStorageWarning() {
// To show errors
var warning = -1
val state = Environment.getExternalStorageState()
if (state == Environment.MEDIA_MOUNTED_READ_ONLY) {
warning = R.string.read_only_warning
} else if (state != Environment.MEDIA_MOUNTED) {
warning = R.string.warning_unmounted
}
val labelWarningView = findViewById<TextView>(R.id.label_warning)
if (warning != -1) {
labelWarningView.setText(warning)
labelWarningView.visibility = View.VISIBLE
} else {
labelWarningView.visibility = View.INVISIBLE
}
}
override fun onResume() {
val database = Database.getInstance()
if (database.loaded) {
launchGroupActivity(database.isReadOnly)
}
super.onResume()
updateExternalStorageWarning()
// Construct adapter with listeners
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let {
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
updateFileListVisibility()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
// Register progress task
mProgressDialogThread?.registerProgressTask()
}
override fun onPause() {
// Unregister progress task
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// only to keep the current activity
outState.putBoolean(EXTRA_STAY, true)
// to retrieve the URI of a created database after an orientation change
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
}
private fun updateFileListVisibility() {
if (mAdapterDatabaseHistory?.itemCount == 0)
fileListContainer?.visibility = View.INVISIBLE
else
fileListContainer?.visibility = View.VISIBLE
}
override fun onAssignKeyDialogPositiveClick(
masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?) {
try {
mDatabaseFileUri?.let { databaseUri ->
// Create the new database
mProgressDialogThread?.startDatabaseCreate(
databaseUri,
masterPasswordChecked,
masterPassword,
keyFileChecked,
keyFile
)
}
} catch (e: Exception) {
val error = getString(R.string.error_create_database_file)
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error, e)
}
}
override fun onAssignKeyDialogNegativeClick(
masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?) {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
if (uri != null) {
launchPasswordActivityWithPath(uri)
}
}
// Retrieve the created URI from the file manager
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
}
// else {
// TODO Show error
// }
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
MenuUtil.defaultMenuInflater(menuInflater, menu)
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
return true
}
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
// If no recent files
val createDatabaseEducationPerformed = createButtonView != null && createButtonView!!.visibility == View.VISIBLE
&& mAdapterDatabaseHistory != null
&& mAdapterDatabaseHistory!!.itemCount > 0
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createButtonView!!,
{
createNewFile()
},
{
// But if the user cancel, it can also select a database
performedNextEducation(fileDatabaseSelectActivityEducation)
})
if (!createDatabaseEducationPerformed) {
// selectDatabaseEducationPerformed
openDatabaseButtonView != null
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
openDatabaseButtonView!!,
{tapTargetView ->
tapTargetView?.let {
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
}
},
{}
)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
}
companion object {
private const val TAG = "FileDbSelectActivity"
private const val EXTRA_STAY = "EXTRA_STAY"
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
/*
* -------------------------
* No Standard Launch, pass by PasswordActivity
* -------------------------
*/
/*
* -------------------------
* Keyboard Launch
* -------------------------
*/
fun launchForKeyboardSelection(activity: Activity) {
EntrySelectionHelper.startActivityForEntrySelection(activity, Intent(activity, FileDatabaseSelectActivity::class.java))
}
/*
* -------------------------
* Autofill Launch
* -------------------------
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure) {
AutofillHelper.startActivityForAutofillResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java),
assistStructure)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
package com.kunzisoft.keepass.activities;
import android.content.Intent;
public interface IntentBuildLauncher {
void startActivityForResult(Intent intent);
}

View File

@@ -1,297 +0,0 @@
package com.kunzisoft.keepass.activities;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.stylish.StylishFragment;
public class ListNodesFragment extends StylishFragment implements
SortDialogFragment.SortSelectionListener {
private static final String TAG = ListNodesFragment.class.getName();
private static final String GROUP_KEY = "GROUP_KEY";
private static final String IS_SEARCH = "IS_SEARCH";
private NodeAdapter.NodeClickCallback nodeClickCallback;
private NodeAdapter.NodeMenuListener nodeMenuListener;
private OnScrollListener onScrollListener;
private RecyclerView listView;
private PwGroup currentGroup;
private NodeAdapter mAdapter;
private View notFoundView;
private boolean isASearchResult;
// Preferences for sorting
private SharedPreferences prefs;
private boolean readOnly;
public static ListNodesFragment newInstance(PwGroup group, boolean readOnly, boolean isASearch) {
Bundle bundle = new Bundle();
if (group != null) {
bundle.putParcelable(GROUP_KEY, group);
}
bundle.putBoolean(IS_SEARCH, isASearch);
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly);
ListNodesFragment listNodesFragment = new ListNodesFragment();
listNodesFragment.setArguments(bundle);
return listNodesFragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
nodeClickCallback = (NodeAdapter.NodeClickCallback) context;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(context.toString()
+ " must implement " + NodeAdapter.NodeClickCallback.class.getName());
}
try {
nodeMenuListener = (NodeAdapter.NodeMenuListener) context;
} catch (ClassCastException e) {
nodeMenuListener = null;
// Context menu can be omit
Log.w(TAG, context.toString()
+ " must implement " + NodeAdapter.NodeMenuListener.class.getName());
}
try {
onScrollListener = (OnScrollListener) context;
} catch (ClassCastException e) {
onScrollListener = null;
// Context menu can be omit
Log.w(TAG, context.toString()
+ " must implement " + RecyclerView.OnScrollListener.class.getName());
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( getActivity() != null ) {
setHasOptionsMenu(true);
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
if (getArguments() != null) {
// Contains all the group in element
if (getArguments().containsKey(GROUP_KEY)) {
currentGroup = getArguments().getParcelable(GROUP_KEY);
}
if (getArguments().containsKey(IS_SEARCH)) {
isASearchResult = getArguments().getBoolean(IS_SEARCH);
}
}
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
mAdapter.setReadOnly(readOnly);
mAdapter.setIsASearchResult(isASearchResult);
mAdapter.setOnNodeClickListener(nodeClickCallback);
if (nodeMenuListener != null) {
mAdapter.setActivateContextMenu(true);
mAdapter.setNodeMenuListener(nodeMenuListener);
}
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// To apply theme
View rootView = inflater.cloneInContext(getContextThemed())
.inflate(R.layout.list_nodes_fragment, container, false);
listView = rootView.findViewById(R.id.nodes_list);
notFoundView = rootView.findViewById(R.id.not_found_container);
if (onScrollListener != null) {
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
onScrollListener.onScrolled(dy);
}
});
}
return rootView;
}
@Override
public void onResume() {
super.onResume();
rebuildList();
if (isASearchResult && mAdapter.isEmpty()) {
// To show the " no search entry found "
listView.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listView.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
}
public void rebuildList() {
// Add elements to the list
if (currentGroup != null)
mAdapter.rebuildList(currentGroup);
assignListToNodeAdapter(listView);
}
protected void assignListToNodeAdapter(RecyclerView recyclerView) {
recyclerView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(mAdapter);
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
// Toggle setting
SharedPreferences.Editor editor = prefs.edit();
editor.putString(getString(R.string.sort_node_key), sortNodeEnum.name());
editor.putBoolean(getString(R.string.sort_ascending_key), ascending);
editor.putBoolean(getString(R.string.sort_group_before_key), groupsBefore);
editor.putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom);
editor.apply();
// Tell the adapter to refresh it's list
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
mAdapter.rebuildList(currentGroup);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.tree, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_sort:
SortDialogFragment sortDialogFragment;
PwDatabase database = App.getDB().getPwDatabase();
/*
// TODO Recycle bin bottom
if (database.isRecycleBinAvailable() && database.isRecycleBinEnabled()) {
sortDialogFragment =
SortDialogFragment.getInstance(
PrefsUtil.getListSort(this),
PrefsUtil.getAscendingSort(this),
PrefsUtil.getGroupsBeforeSort(this),
PrefsUtil.getRecycleBinBottomSort(this));
} else {
*/
sortDialogFragment =
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(getContext()),
PreferencesUtil.getAscendingSort(getContext()),
PreferencesUtil.getGroupsBeforeSort(getContext()));
//}
sortDialogFragment.show(getChildFragmentManager(), "sortDialog");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
PwNode newNode = data.getParcelableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
if (newNode != null) {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter.addNode(newNode);
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode);
mAdapter.rebuildList(currentGroup);
}
} else {
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
}
}
break;
}
}
public boolean isEmpty() {
return mAdapter == null || mAdapter.getItemCount() <= 0;
}
public void addNode(PwNode newNode) {
mAdapter.addNode(newNode);
}
public void updateNode(PwNode oldNode, PwNode newNode) {
mAdapter.updateNode(oldNode, newNode);
}
public void removeNode(PwNode pwNode) {
mAdapter.removeNode(pwNode);
}
public PwGroup getMainGroup() {
return currentGroup;
}
public interface OnScrollListener {
/**
* Callback method to be invoked when the RecyclerView has been scrolled. This will be
* called after the scroll has completed.
*
* @param dy The amount of vertical scroll.
*/
void onScrolled(int dy);
}
}

View File

@@ -0,0 +1,481 @@
/*
* 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
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.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.node.Type
import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
private var nodeClickListener: NodeClickListener? = null
private var onScrollListener: OnScrollListener? = null
private var listView: RecyclerView? = null
var mainGroup: Group? = null
private set
private var mAdapter: NodeAdapter? = null
var nodeActionSelectionMode = false
private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
private set
private val listActionNodes = LinkedList<Node>()
private val listPasteNodes = LinkedList<Node>()
private var notFoundView: View? = null
private var isASearchResult: Boolean = false
// Preferences for sorting
private var prefs: SharedPreferences? = null
private var readOnly: Boolean = false
get() {
return field || selectionMode
}
private var selectionMode: Boolean = false
val isEmpty: Boolean
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
override fun onAttach(context: Context) {
super.onAttach(context)
try {
nodeClickListener = context as NodeClickListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + NodeAdapter.NodeClickCallback::class.java.name)
}
try {
onScrollListener = context as OnScrollListener
} catch (e: ClassCastException) {
onScrollListener = null
// Context menu can be omit
Log.w(TAG, context.toString()
+ " must implement " + RecyclerView.OnScrollListener::class.java.name)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
arguments?.let { args ->
// Contains all the group in element
if (args.containsKey(GROUP_KEY)) {
mainGroup = args.getParcelable(GROUP_KEY)
}
if (args.containsKey(IS_SEARCH)) {
isASearchResult = args.getBoolean(IS_SEARCH)
}
}
contextThemed?.let { context ->
mAdapter = NodeAdapter(context)
mAdapter?.apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(node: Node) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
listActionNodes.remove(node)
} else {
// Add selected item if not already selected
listActionNodes.add(node)
}
nodeClickListener?.onNodeSelected(listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
} else {
nodeClickListener?.onNodeClick(node)
}
}
override fun onNodeLongClick(node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
listActionNodes.add(node)
nodeClickListener?.onNodeSelected(listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
}
return true
}
})
}
}
prefs = PreferenceManager.getDefaultSharedPreferences(context)
}
override fun onSaveInstanceState(outState: Bundle) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
// To apply theme
val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_list_nodes, container, false)
listView = rootView.findViewById(R.id.nodes_list)
notFoundView = rootView.findViewById(R.id.not_found_container)
onScrollListener?.let { onScrollListener ->
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrollListener.onScrolled(dy)
}
})
}
rebuildList()
return rootView
}
override fun onResume() {
super.onResume()
activity?.intent?.let {
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(it)
}
// Refresh data
mAdapter?.notifyDataSetChanged()
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found "
listView?.visibility = View.GONE
notFoundView?.visibility = View.VISIBLE
} else {
listView?.visibility = View.VISIBLE
notFoundView?.visibility = View.GONE
}
}
fun rebuildList() {
// Add elements to the list
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
listView?.apply {
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
layoutManager = LinearLayoutManager(context)
adapter = mAdapter
}
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
// Toggle setting
prefs?.edit()?.apply {
putString(getString(R.string.sort_node_key), sortNodeEnum.name)
putBoolean(getString(R.string.sort_ascending_key), ascending)
putBoolean(getString(R.string.sort_group_before_key), groupsBefore)
putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom)
apply()
}
// Tell the adapter to refresh it's list
mAdapter?.notifyChangeSort(sortNodeEnum, ascending, groupsBefore)
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.tree, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (Database.getInstance().isRecycleBinEnabled) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context))
} else {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context))
}
sortDialogFragment.show(childFragmentManager, "sortDialog")
}
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
fun actionNodesCallback(nodes: List<Node>,
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
return object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
nodeActionSelectionMode = false
nodeActionPasteMode = PasteMode.UNDEFINED
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
menu?.clear()
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
} else {
nodeActionSelectionMode = true
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
val database = Database.getInstance()
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (readOnly
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
menu?.removeItem(R.id.menu_edit)
}
} else {
menu?.removeItem(R.id.menu_open)
menu?.removeItem(R.id.menu_edit)
}
// Copy and Move (not for groups)
if (readOnly
|| isASearchResult
|| nodes.any { it.type == Type.GROUP }) {
// TODO COPY For Group
menu?.removeItem(R.id.menu_copy)
menu?.removeItem(R.id.menu_move)
}
// Deletion
if (readOnly
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
menu?.removeItem(R.id.menu_delete)
}
}
// Add the number of items selected in title
mode?.title = nodes.size.toString()
return true
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
if (menuListener == null)
return false
return when (item?.itemId) {
R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0])
R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0])
R.id.menu_copy -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_COPY
mAdapter?.unselectActionNodes()
val returnValue = menuListener.onCopyMenuClick(nodes)
nodeActionSelectionMode = false
returnValue
}
R.id.menu_move -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE
mAdapter?.unselectActionNodes()
val returnValue = menuListener.onMoveMenuClick(nodes)
nodeActionSelectionMode = false
returnValue
}
R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes)
R.id.menu_paste -> {
val returnValue = menuListener.onPasteMenuClick(nodeActionPasteMode, nodes)
nodeActionPasteMode = PasteMode.UNDEFINED
nodeActionSelectionMode = false
returnValue
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
listActionNodes.clear()
listPasteNodes.clear()
mAdapter?.unselectActionNodes()
nodeActionPasteMode = PasteMode.UNDEFINED
nodeActionSelectionMode = false
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
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) {
//mAdapter.updateLastNodeRegister(newNode);
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
}
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
}
}
}
}
fun contains(node: Node): Boolean {
return mAdapter?.contains(node) ?: false
}
fun addNode(newNode: Node) {
mAdapter?.addNode(newNode)
}
fun addNodes(newNodes: List<Node>) {
mAdapter?.addNodes(newNodes)
}
fun updateNode(oldNode: Node, newNode: Node? = null) {
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
}
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
mAdapter?.updateNodes(oldNodes, newNodes)
}
fun removeNode(pwNode: Node) {
mAdapter?.removeNode(pwNode)
}
fun removeNodes(nodes: List<Node>) {
mAdapter?.removeNodes(nodes)
}
fun removeNodeAt(position: Int) {
mAdapter?.removeNodeAt(position)
}
fun removeNodesAt(positions: IntArray) {
mAdapter?.removeNodesAt(positions)
}
/**
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickListener {
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: 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 {
UNDEFINED, PASTE_FROM_COPY, PASTE_FROM_MOVE
}
interface OnScrollListener {
/**
* Callback method to be invoked when the RecyclerView has been scrolled. This will be
* called after the scroll has completed.
*
* @param dy The amount of vertical scroll.
*/
fun onScrolled(dy: Int)
}
companion object {
private val TAG = ListNodesFragment::class.java.name
private const val GROUP_KEY = "GROUP_KEY"
private const val IS_SEARCH = "IS_SEARCH"
fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
val bundle = Bundle()
if (group != null) {
bundle.putParcelable(GROUP_KEY, group)
}
bundle.putBoolean(IS_SEARCH, isASearch)
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly)
val listNodesFragment = ListNodesFragment()
listNodesFragment.arguments = bundle
return listNodesFragment
}
}
}

View File

@@ -0,0 +1,800 @@
/*
* 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.app.Activity
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
import android.os.Handler
import android.preference.PreferenceManager
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
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
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
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.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
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.KEY_FILE_KEY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.FileDatabaseInfo
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException
class PasswordActivity : StylishActivity() {
// Views
private var toolbar: Toolbar? = null
private var containerView: View? = null
private var filenameView: TextView? = null
private var passwordView: EditText? = null
private var keyFileView: EditText? = null
private var confirmButtonView: Button? = null
private var checkboxPasswordView: CompoundButton? = null
private var checkboxKeyFileView: CompoundButton? = null
private var checkboxDefaultDatabaseView: CompoundButton? = null
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
private var prefs: SharedPreferences? = null
private var mRememberKeyFile: Boolean = false
private var mOpenFileHelper: OpenFileHelper? = null
private var mPermissionAsked = false
private var readOnly: Boolean = false
private var mProgressDialogThread: ProgressDialogThread? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = PreferenceManager.getDefaultSharedPreferences(this)
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
setContentView(R.layout.activity_password)
toolbar = findViewById(R.id.toolbar)
toolbar?.title = getString(R.string.app_name)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
containerView = findViewById(R.id.container)
confirmButtonView = findViewById(R.id.pass_ok)
filenameView = findViewById(R.id.filename)
passwordView = findViewById(R.id.password)
keyFileView = findViewById(R.id.pass_keyfile)
checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
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)
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
if (editable.toString().isNotEmpty() && checkboxPasswordView?.isChecked != true)
checkboxPasswordView?.isChecked = true
}
})
keyFileView?.setOnEditorActionListener(onEditorActionListener)
keyFileView?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
if (editable.toString().isNotEmpty() && checkboxKeyFileView?.isChecked != true)
checkboxKeyFileView?.isChecked = true
}
})
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
enableOrNotTheConfirmationButton()
}
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck biometric if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
}
// Remove the password in view in all cases
removePassword()
if (result.isSuccess) {
launchGroupActivity()
} else {
var resultError = ""
val resultException = result.exception
val resultMessage = result.message
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
// Relaunch loading if we need to fix UUID
if (resultException is DuplicateUuidDatabaseException) {
showLoadDatabaseDuplicateUuidMessage {
var databaseUri: Uri? = null
var masterPassword: String? = null
var keyFileUri: Uri? = null
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
masterPassword,
keyFileUri,
readOnly,
cipherEntity,
true)
}
}
}
}
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError, resultException)
Snackbar.make(activity_password_coordinator_layout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
}
}
}
}
}
}
private fun launchGroupActivity() {
EntrySelectionHelper.doEntrySelectionAction(intent,
{
GroupActivity.launch(this@PasswordActivity, readOnly)
},
{
GroupActivity.launchForKeyboardSelection(this@PasswordActivity, readOnly)
// Do not keep history
finish()
},
{ assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly)
}
})
}
private val onEditorActionListener = object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == IME_ACTION_DONE) {
verifyCheckboxesAndLoadDatabase()
return true
}
return false
}
}
override fun onResume() {
if (Database.getInstance().loaded)
launchGroupActivity()
// If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state
if (Database.getInstance().loaded) {
setEmptyViews()
}
// For check shutdown
super.onResume()
mProgressDialogThread?.registerProgressTask()
initUriFromIntent()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState)
}
private fun initUriFromIntent() {
val databaseUri: Uri?
val keyFileUri: Uri?
// If is a view intent
val action = intent.action
if (action != null
&& action == VIEW_INTENT) {
databaseUri = intent.data
keyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
} else {
databaseUri = intent.getParcelableExtra(KEY_FILENAME)
keyFileUri = intent.getParcelableExtra(KEY_KEYFILE)
}
// Post init uri with KeyFile if needed
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
// Retrieve KeyFile in a thread
databaseUri?.let { databaseUriNotNull ->
FileDatabaseHistoryAction.getInstance(applicationContext)
.getKeyFileUriByDatabaseUri(databaseUriNotNull) {
onPostInitUri(databaseUri, it)
}
}
} else {
onPostInitUri(databaseUri, keyFileUri)
}
}
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
mDatabaseFileUri = databaseFileUri
mDatabaseKeyFileUri = keyFileUri
// Define title
databaseFileUri?.let {
FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
filenameView?.text = title
}
}
// Define Key File text
val keyUriString = keyFileUri?.toString() ?: ""
if (keyUriString.isNotEmpty() && mRememberKeyFile) { // Bug KeepassDX #18
populateKeyFileTextView(keyUriString)
}
// Define listeners for default database checkbox and validate button
checkboxDefaultDatabaseView?.setOnCheckedChangeListener { _, isChecked ->
var newDefaultFileName: Uri? = null
if (isChecked) {
newDefaultFileName = databaseFileUri ?: newDefaultFileName
}
prefs?.edit()?.apply {
newDefaultFileName?.let {
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
} ?: kotlin.run {
remove(KEY_DEFAULT_DATABASE_PATH)
}
apply()
}
val backupManager = BackupManager(this@PasswordActivity)
backupManager.dataChanged()
}
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
// Retrieve settings for default database
val defaultFilename = prefs?.getString(KEY_DEFAULT_DATABASE_PATH, "")
if (databaseFileUri != null
&& databaseFileUri.path != null && databaseFileUri.path!!.isNotEmpty()
&& databaseFileUri == UriUtil.parse(defaultFilename)) {
checkboxDefaultDatabaseView?.isChecked = true
}
// If Activity is launch with a password and want to open directly
val intent = intent
val password = intent.getStringExtra(KEY_PASSWORD)
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
if (password != null) {
populatePasswordTextView(password)
}
if (launchImmediately) {
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else {
// Init Biometric elements
var biometricInitialize = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this)) {
if (advancedUnlockedManager == null && databaseFileUri != null) {
advancedUnlockedManager = AdvancedUnlockedManager(this,
databaseFileUri,
advancedUnlockInfoView,
checkboxPasswordView,
enableButtonOnCheckedChangeListener,
passwordView,
{ passwordEncrypted, ivSpec ->
// Load the database if password is registered with biometric
if (passwordEncrypted != null && ivSpec != null) {
verifyCheckboxesAndLoadDatabase(
CipherDatabaseEntity(
databaseFileUri.toString(),
passwordEncrypted,
ivSpec)
)
}
},
{ passwordDecrypted ->
// Load the database if password is retrieve from biometric
passwordDecrypted?.let {
// Retrieve from biometric
verifyKeyFileCheckboxAndLoadDatabase(it)
}
})
}
advancedUnlockedManager?.checkBiometricAvailability()
biometricInitialize = true
} else {
advancedUnlockedManager?.destroy()
}
}
if (!biometricInitialize) {
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
}
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
}
enableOrNotTheConfirmationButton()
}
private fun enableOrNotTheConfirmationButton() {
// Enable or not the open button if setting is checked
if (!PreferencesUtil.emptyPasswordAllowed(this@PasswordActivity)) {
checkboxPasswordView?.let {
confirmButtonView?.isEnabled = (checkboxPasswordView?.isChecked == true
|| checkboxKeyFileView?.isChecked == true)
}
} else {
confirmButtonView?.isEnabled = true
}
}
private fun setEmptyViews() {
populatePasswordTextView(null)
// Bug KeepassDX #18
if (!mRememberKeyFile) {
populateKeyFileTextView(null)
}
}
private fun populatePasswordTextView(text: String?) {
if (text == null || text.isEmpty()) {
passwordView?.setText("")
if (checkboxPasswordView?.isChecked == true)
checkboxPasswordView?.isChecked = false
} else {
passwordView?.setText(text)
if (checkboxPasswordView?.isChecked != true)
checkboxPasswordView?.isChecked = true
}
}
private fun populateKeyFileTextView(text: String?) {
if (text == null || text.isEmpty()) {
keyFileView?.setText("")
if (checkboxKeyFileView?.isChecked == true)
checkboxKeyFileView?.isChecked = false
} else {
keyFileView?.setText(text)
if (checkboxKeyFileView?.isChecked != true)
checkboxKeyFileView?.isChecked = true
}
}
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.destroy()
}
super.onDestroy()
}
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
val password: String? = passwordView?.text?.toString()
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
}
private fun verifyCheckboxesAndLoadDatabase(password: String?,
keyFile: Uri?,
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
verifyKeyFileCheckbox(keyFile)
loadDatabase(mDatabaseFileUri, keyPassword, mDatabaseKeyFileUri, cipherDatabaseEntity)
}
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
verifyKeyFileCheckbox(keyFile)
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
}
private fun verifyKeyFileCheckbox(keyFile: Uri?) {
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
}
private fun removePassword() {
passwordView?.setText("")
checkboxPasswordView?.isChecked = false
}
private fun loadDatabase(databaseFileUri: Uri?,
password: String?,
keyFileUri: Uri?,
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
removePassword()
}
databaseFileUri?.let { databaseUri ->
// Show the progress dialog and load the database
showProgressDialogAndLoadDatabase(
databaseUri,
password,
keyFileUri,
readOnly,
cipherDatabaseEntity,
false)
}
}
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
password: String?,
keyFile: Uri?,
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) {
mProgressDialogThread?.startDatabaseLoad(
databaseUri,
password,
keyFile,
readOnly,
cipherDatabaseEntity,
fixDuplicateUUID
)
}
private fun showLoadDatabaseDuplicateUuidMessage(loadDatabaseWithFix: (() -> Unit)? = null) {
DuplicateUuidDialog().apply {
positiveAction = loadDatabaseWithFix
}.show(supportFragmentManager, "duplicateUUIDDialog")
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
// Read menu
inflater.inflate(R.menu.open_file, menu)
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
MenuUtil.defaultMenuInflater(inflater, menu)
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
}
super.onCreateOptionsMenu(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,
onEducationFinished: ()-> Unit) {
val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
educationToolbar,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!unlockEducationPerformed) {
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, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
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!!,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!biometricEducationPerformed) {
onEducationFinished.invoke()
}
}
}
}
private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
if (readOnly) {
togglePassword.setTitle(R.string.menu_file_selection_read_only)
togglePassword.setIcon(R.drawable.ic_read_only_white_24dp)
} else {
togglePassword.setTitle(R.string.menu_open_file_read_and_write)
togglePassword.setIcon(R.drawable.ic_read_write_white_24dp)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
R.id.menu_open_file_read_mode_key -> {
readOnly = !readOnly
changeOpenFileReadIcon(item)
}
R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.deleteEntryKey()
}
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
}
return super.onOptionsItemSelected(item)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// To get entry in result
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
var keyFileResult = false
mOpenFileHelper?.let {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
if (uri != null) {
populateKeyFileTextView(uri.toString())
}
}
}
if (!keyFileResult) {
// this block if not a key file response
when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK, Activity.RESULT_CANCELED -> {
setEmptyViews()
Database.getInstance().closeAndClear(applicationContext.filesDir)
}
}
}
}
companion object {
private val TAG = PasswordActivity::class.java.name
const val KEY_DEFAULT_DATABASE_PATH = "KEY_DEFAULT_DATABASE_PATH"
private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW"
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)
intent.putExtra(KEY_FILENAME, databaseFile)
if (keyFile != null)
intent.putExtra(KEY_KEYFILE, keyFile)
intentBuildLauncher.invoke(intent)
}
/*
* -------------------------
* Standard Launch
* -------------------------
*/
@Throws(FileNotFoundException::class)
fun launch(
activity: Activity,
databaseFile: Uri,
keyFile: Uri?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
activity.startActivity(intent)
}
}
/*
* -------------------------
* Keyboard Launch
* -------------------------
*/
@Throws(FileNotFoundException::class)
fun launchForKeyboardResult(
activity: Activity,
databaseFile: Uri,
keyFile: Uri?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
EntrySelectionHelper.startActivityForEntrySelection(activity, intent)
}
}
/*
* -------------------------
* Autofill Launch
* -------------------------
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@Throws(FileNotFoundException::class)
fun launchForAutofillResult(
activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
assistStructure: AssistStructure?) {
if (assistStructure != null) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
assistStructure)
}
} else {
launch(activity, databaseFile, keyFile)
}
}
}
}

View File

@@ -1,61 +0,0 @@
package com.kunzisoft.keepass.activities;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.kunzisoft.keepass.settings.PreferencesUtil;
public class ReadOnlyHelper {
public static final String READ_ONLY_KEY = "READ_ONLY_KEY";
public static final boolean READ_ONLY_DEFAULT = false;
public static boolean retrieveReadOnlyFromInstanceStateOrPreference(Context context, Bundle savedInstanceState) {
boolean readOnly;
if (savedInstanceState != null
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
} else {
readOnly = PreferencesUtil.enableReadOnlyDatabase(context);
}
return readOnly;
}
public static boolean retrieveReadOnlyFromInstanceStateOrArguments(Bundle savedInstanceState, Bundle arguments) {
boolean readOnly = READ_ONLY_DEFAULT;
if (savedInstanceState != null
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
} else if (arguments != null
&& arguments.containsKey(READ_ONLY_KEY)) {
readOnly = arguments.getBoolean(READ_ONLY_KEY);
}
return readOnly;
}
public static boolean retrieveReadOnlyFromInstanceStateOrIntent(Bundle savedInstanceState, Intent intent) {
boolean readOnly = READ_ONLY_DEFAULT;
if (savedInstanceState != null
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
} else {
if (intent != null)
readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT);
}
return readOnly;
}
public static void putReadOnlyInIntent(Intent intent, boolean readOnly) {
intent.putExtra(READ_ONLY_KEY, readOnly);
}
public static void putReadOnlyInBundle(Bundle bundle, boolean readOnly) {
bundle.putBoolean(READ_ONLY_KEY, readOnly);
}
public static void onSaveInstanceState(Bundle outState, boolean readOnly) {
outState.putBoolean(READ_ONLY_KEY, readOnly);
}
}

View File

@@ -0,0 +1,286 @@
/*
* 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.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import com.google.android.material.textfield.TextInputLayout
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.CompoundButton
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.utils.UriUtil
class AssignMasterKeyDialogFragment : DialogFragment() {
private var mMasterPassword: String? = null
private var mKeyFile: Uri? = null
private var rootView: View? = null
private var passwordCheckBox: CompoundButton? = null
private var passwordTextInputLayout: TextInputLayout? = null
private var passwordView: TextView? = null
private var passwordRepeatTextInputLayout: TextInputLayout? = null
private var passwordRepeatView: TextView? = null
private var keyFileTextInputLayout: TextInputLayout? = null
private var keyFileCheckBox: CompoundButton? = null
private var keyFileView: TextView? = null
private var mListener: AssignPasswordDialogListener? = null
private var mOpenFileHelper: OpenFileHelper? = null
private val passwordTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
passwordCheckBox?.isChecked = true
}
}
private val keyFileTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
keyFileCheckBox?.isChecked = true
}
}
interface AssignPasswordDialogListener {
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?)
fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?)
}
override fun onAttach(activity: Context) {
super.onAttach(activity)
try {
mListener = activity as AssignPasswordDialogListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString()
+ " must implement " + AssignPasswordDialogListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
var allowNoMasterKey = false
arguments?.apply {
if (containsKey(ALLOW_NO_MASTER_KEY_ARG))
allowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
}
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
rootView = inflater.inflate(R.layout.fragment_set_password, null)
builder.setView(rootView)
.setTitle(R.string.assign_master_key)
// Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
passwordView = rootView?.findViewById(R.id.pass_password)
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
keyFileTextInputLayout = rootView?.findViewById(R.id.keyfile_input_layout)
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
mOpenFileHelper = OpenFileHelper(this)
rootView?.findViewById<View>(R.id.open_database_button)?.setOnClickListener { view ->
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
val dialog = builder.create()
if (passwordCheckBox != null && keyFileCheckBox!= null) {
dialog.setOnShowListener { dialog1 ->
val positiveButton = (dialog1 as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
positiveButton.setOnClickListener {
mMasterPassword = ""
mKeyFile = null
var error = verifyPassword() || verifyFile()
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
error = true
if (allowNoMasterKey)
showNoKeyConfirmationDialog()
else {
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
}
}
if (!error) {
mListener?.onAssignKeyDialogPositiveClick(
passwordCheckBox!!.isChecked, mMasterPassword,
keyFileCheckBox!!.isChecked, mKeyFile)
dismiss()
}
}
val negativeButton = dialog1.getButton(DialogInterface.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
mListener?.onAssignKeyDialogNegativeClick(
passwordCheckBox!!.isChecked, mMasterPassword,
keyFileCheckBox!!.isChecked, mKeyFile)
dismiss()
}
}
}
return dialog
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
// To check checkboxes if a text is present
passwordView?.addTextChangedListener(passwordTextWatcher)
keyFileView?.addTextChangedListener(keyFileTextWatcher)
}
override fun onPause() {
super.onPause()
passwordView?.removeTextChangedListener(passwordTextWatcher)
keyFileView?.removeTextChangedListener(keyFileTextWatcher)
}
private fun verifyPassword(): Boolean {
var error = false
if (passwordCheckBox != null
&& passwordCheckBox!!.isChecked
&& passwordView != null
&& passwordRepeatView != null) {
mMasterPassword = passwordView!!.text.toString()
val confPassword = passwordRepeatView!!.text.toString()
// Verify that passwords match
if (mMasterPassword != confPassword) {
error = true
// Passwords do not match
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
}
if (mMasterPassword == null || mMasterPassword!!.isEmpty()) {
error = true
showEmptyPasswordConfirmationDialog()
}
}
return error
}
private fun verifyFile(): Boolean {
var error = false
if (keyFileCheckBox != null
&& keyFileCheckBox!!.isChecked) {
UriUtil.parse(keyFileView?.text?.toString())?.let { uri ->
mKeyFile = uri
} ?: run {
error = true
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile)
}
}
return error
}
private fun showEmptyPasswordConfirmationDialog() {
activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.warning_empty_password)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (!verifyFile()) {
mListener?.onAssignKeyDialogPositiveClick(
passwordCheckBox!!.isChecked, mMasterPassword,
keyFileCheckBox!!.isChecked, mKeyFile)
this@AssignMasterKeyDialogFragment.dismiss()
}
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
builder.create().show()
}
}
private fun showNoKeyConfirmationDialog() {
activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.warning_no_encryption_key)
.setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.onAssignKeyDialogPositiveClick(
passwordCheckBox!!.isChecked, mMasterPassword,
keyFileCheckBox!!.isChecked, mKeyFile)
this@AssignMasterKeyDialogFragment.dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
builder.create().show()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
uri?.let { pathUri ->
keyFileCheckBox?.isChecked = true
keyFileView?.text = pathUri.toString()
}
}
}
companion object {
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"
fun getInstance(allowNoMasterKey: Boolean): AssignMasterKeyDialogFragment {
val fragment = AssignMasterKeyDialogFragment()
val args = Bundle()
args.putBoolean(ALLOW_NO_MASTER_KEY_ARG, allowNoMasterKey)
fragment.arguments = args
return fragment
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.widget.Button
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
class BrowserDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
// Get the layout inflater
val root = activity.layoutInflater.inflate(R.layout.fragment_browser_install, null)
builder.setView(root)
.setNegativeButton(android.R.string.cancel) { _, _ -> }
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
textDescription.text = getString(R.string.file_manager_install_description)
val market = root.findViewById<Button>(R.id.file_manager_install_play_store)
market.setOnClickListener {
UriUtil.gotoUrl(context!!, R.string.file_manager_play_store)
dismiss()
}
val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
web.setOnClickListener {
UriUtil.gotoUrl(context!!, R.string.file_manager_f_droid)
dismiss()
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

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

@@ -0,0 +1,54 @@
/*
* 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.os.Bundle
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class DuplicateUuidDialog : DialogFragment() {
var positiveAction: (() -> Unit)? = null
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).apply {
val message = getString(R.string.contains_duplicate_uuid) +
"\n\n" + getString(R.string.contains_duplicate_uuid_procedure)
setMessage(message)
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
positiveAction?.invoke()
dismiss()
}
setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onPause() {
super.onPause()
this.dismiss()
}
}

View File

@@ -0,0 +1,199 @@
/*
* 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 com.google.android.material.textfield.TextInputLayout
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.widget.Button
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.SeekBar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.applyFontVisibility
class GeneratePasswordDialogFragment : DialogFragment() {
private var mListener: GeneratePasswordListener? = null
private var root: View? = null
private var lengthTextView: EditText? = null
private var passwordInputLayoutView: TextInputLayout? = null
private var passwordView: EditText? = null
private var uppercaseBox: CompoundButton? = null
private var lowercaseBox: CompoundButton? = null
private var digitsBox: CompoundButton? = null
private var minusBox: CompoundButton? = null
private var underlineBox: CompoundButton? = null
private var spaceBox: CompoundButton? = null
private var specialsBox: CompoundButton? = null
private var bracketsBox: CompoundButton? = null
private var extendedBox: CompoundButton? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as GeneratePasswordListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + GeneratePasswordListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
root = inflater.inflate(R.layout.fragment_generate_password, null)
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
passwordView = root?.findViewById(R.id.password)
passwordView?.applyFontVisibility()
lengthTextView = root?.findViewById(R.id.length)
uppercaseBox = root?.findViewById(R.id.cb_uppercase)
lowercaseBox = root?.findViewById(R.id.cb_lowercase)
digitsBox = root?.findViewById(R.id.cb_digits)
minusBox = root?.findViewById(R.id.cb_minus)
underlineBox = root?.findViewById(R.id.cb_underline)
spaceBox = root?.findViewById(R.id.cb_space)
specialsBox = root?.findViewById(R.id.cb_specials)
bracketsBox = root?.findViewById(R.id.cb_brackets)
extendedBox = root?.findViewById(R.id.cb_extended)
assignDefaultCharacters()
val seekBar = root?.findViewById<SeekBar>(R.id.seekbar_length)
seekBar?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
lengthTextView?.setText(progress.toString())
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
context?.let { context ->
seekBar?.progress = PreferencesUtil.getDefaultPasswordLength(context)
}
root?.findViewById<Button>(R.id.generate_password_button)
?.setOnClickListener { fillPassword() }
builder.setView(root)
.setPositiveButton(R.string.accept) { _, _ ->
val bundle = Bundle()
bundle.putString(KEY_PASSWORD_ID, passwordView!!.text.toString())
mListener?.acceptPassword(bundle)
dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
val bundle = Bundle()
mListener?.cancelPassword(bundle)
dismiss()
}
// Pre-populate a password to possibly save the user a few clicks
fillPassword()
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun assignDefaultCharacters() {
uppercaseBox?.isChecked = false
lowercaseBox?.isChecked = false
digitsBox?.isChecked = false
minusBox?.isChecked = false
underlineBox?.isChecked = false
spaceBox?.isChecked = false
specialsBox?.isChecked = false
bracketsBox?.isChecked = false
extendedBox?.isChecked = false
context?.let { context ->
PreferencesUtil.getDefaultPasswordCharacters(context)?.let { charSet ->
for (passwordChar in charSet) {
when (passwordChar) {
getString(R.string.value_password_uppercase) -> uppercaseBox?.isChecked = true
getString(R.string.value_password_lowercase) -> lowercaseBox?.isChecked = true
getString(R.string.value_password_digits) -> digitsBox?.isChecked = true
getString(R.string.value_password_minus) -> minusBox?.isChecked = true
getString(R.string.value_password_underline) -> underlineBox?.isChecked = true
getString(R.string.value_password_space) -> spaceBox?.isChecked = true
getString(R.string.value_password_special) -> specialsBox?.isChecked = true
getString(R.string.value_password_brackets) -> bracketsBox?.isChecked = true
getString(R.string.value_password_extended) -> extendedBox?.isChecked = true
}
}
}
}
}
private fun fillPassword() {
root?.findViewById<EditText>(R.id.password)?.setText(generatePassword())
}
fun generatePassword(): String {
var password = ""
try {
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
password = PasswordGenerator(resources).generatePassword(length,
uppercaseBox?.isChecked == true,
lowercaseBox?.isChecked == true,
digitsBox?.isChecked == true,
minusBox?.isChecked == true,
underlineBox?.isChecked == true,
spaceBox?.isChecked == true,
specialsBox?.isChecked == true,
bracketsBox?.isChecked == true,
extendedBox?.isChecked == true)
} catch (e: NumberFormatException) {
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
} catch (e: IllegalArgumentException) {
passwordInputLayoutView?.error = e.message
}
return password
}
interface GeneratePasswordListener {
fun acceptPassword(bundle: Bundle)
fun cancelPassword(bundle: Bundle)
}
companion object {
const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID"
}
}

View File

@@ -0,0 +1,219 @@
/*
* 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.graphics.Color
import android.os.Bundle
import com.google.android.material.textfield.TextInputLayout
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
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.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
private var mDatabase: Database? = null
private var editGroupListener: EditGroupListener? = null
private var editGroupDialogAction: EditGroupDialogAction? = null
private var nameGroup: String? = null
private var iconGroup: IconImage? = null
private var nameTextLayoutView: TextInputLayout? = null
private var nameTextView: TextView? = null
private var iconButtonView: ImageView? = null
private var iconColor: Int = 0
enum class EditGroupDialogAction {
CREATION, UPDATE, NONE;
companion object {
fun getActionFromOrdinal(ordinal: Int): EditGroupDialogAction {
return values()[ordinal]
}
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
editGroupListener = context as EditGroupListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + GroupEditDialogFragment::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_group_edit, null)
nameTextLayoutView = root?.findViewById(R.id.group_edit_name_container)
nameTextView = root?.findViewById(R.id.group_edit_name)
iconButtonView = root?.findViewById(R.id.group_edit_icon_button)
// Retrieve the textColor to tint the icon
val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
iconColor = ta.getColor(0, Color.WHITE)
ta.recycle()
// Init elements
mDatabase = Database.getInstance()
editGroupDialogAction = EditGroupDialogAction.NONE
nameGroup = ""
iconGroup = mDatabase?.iconFactory?.folderIcon
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ACTION_ID)
&& savedInstanceState.containsKey(KEY_NAME)
&& savedInstanceState.containsKey(KEY_ICON)) {
editGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID))
nameGroup = savedInstanceState.getString(KEY_NAME)
iconGroup = savedInstanceState.getParcelable(KEY_ICON)
} else {
arguments?.apply {
if (containsKey(KEY_ACTION_ID))
editGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(getInt(KEY_ACTION_ID))
if (containsKey(KEY_NAME) && containsKey(KEY_ICON)) {
nameGroup = getString(KEY_NAME)
iconGroup = getParcelable(KEY_ICON)
}
}
}
// populate the name
nameTextView?.text = nameGroup
// populate the icon
assignIconView()
val builder = AlertDialog.Builder(activity)
builder.setView(root)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(android.R.string.cancel) { _, _ ->
editGroupListener?.cancelEditGroup(
editGroupDialogAction,
nameTextView?.text?.toString(),
iconGroup)
}
iconButtonView?.setOnClickListener { _ ->
fragmentManager?.let {
IconPickerDialogFragment().show(it, "IconPickerDialogFragment")
}
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
// To prevent auto dismiss
val d = dialog as AlertDialog?
if (d != null) {
val positiveButton = d.getButton(Dialog.BUTTON_POSITIVE) as Button
positiveButton.setOnClickListener {
if (isValid()) {
editGroupListener?.approveEditGroup(
editGroupDialogAction,
nameTextView?.text?.toString(),
iconGroup)
d.dismiss()
}
}
}
}
private fun assignIconView() {
if (mDatabase?.drawFactory != null && iconGroup != null) {
iconButtonView?.assignDatabaseIcon(mDatabase?.drawFactory!!, iconGroup!!, iconColor)
}
}
override fun iconPicked(bundle: Bundle) {
iconGroup = IconPickerDialogFragment.getIconStandardFromBundle(bundle)
assignIconView()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(KEY_ACTION_ID, editGroupDialogAction!!.ordinal)
outState.putString(KEY_NAME, nameGroup)
outState.putParcelable(KEY_ICON, iconGroup)
super.onSaveInstanceState(outState)
}
private fun isValid(): Boolean {
if (nameTextView?.text?.toString()?.isNotEmpty() != true) {
nameTextLayoutView?.error = getString(R.string.error_no_name)
return false
}
return true
}
interface EditGroupListener {
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
}
companion object {
const val TAG_CREATE_GROUP = "TAG_CREATE_GROUP"
const val KEY_NAME = "KEY_NAME"
const val KEY_ICON = "KEY_ICON"
const val KEY_ACTION_ID = "KEY_ACTION_ID"
fun build(): GroupEditDialogFragment {
val bundle = Bundle()
bundle.putInt(KEY_ACTION_ID, CREATION.ordinal)
val fragment = GroupEditDialogFragment()
fragment.arguments = bundle
return fragment
}
fun build(group: Group): GroupEditDialogFragment {
val bundle = Bundle()
bundle.putString(KEY_NAME, group.title)
bundle.putParcelable(KEY_ICON, group.icon)
bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal)
val fragment = GroupEditDialogFragment()
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.core.widget.ImageViewCompat
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
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.icon.IconImageStandard
import com.kunzisoft.keepass.icons.IconPack
import com.kunzisoft.keepass.icons.IconPackChooser
class IconPickerDialogFragment : DialogFragment() {
private var iconPickerListener: IconPickerListener? = null
private var iconPack: IconPack? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
iconPickerListener = context as IconPickerListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + IconPickerListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
iconPack = IconPackChooser.getSelectedIconPack(context!!)
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
val root = activity.layoutInflater.inflate(R.layout.fragment_icon_picker, null)
builder.setView(root)
val currIconGridView = root.findViewById<GridView>(R.id.IconGridView)
currIconGridView.adapter = ImageAdapter(activity)
currIconGridView.setOnItemClickListener { _, _, position, _ ->
val bundle = Bundle()
bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
iconPickerListener?.iconPicked(bundle)
dismiss()
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog?.cancel() }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
inner class ImageAdapter internal constructor(private val context: Context) : BaseAdapter() {
override fun getCount(): Int {
return iconPack?.numberOfIcons() ?: 0
}
override fun getItem(position: Int): Any? {
return null
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val currentView: View = convertView
?: (context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
.inflate(R.layout.item_icon, parent, false)
iconPack?.let { iconPack ->
val iconImageView = currentView.findViewById<ImageView>(R.id.icon_image)
iconImageView.setImageResource(iconPack.iconToResId(position))
// Assign color if icons are tintable
if (iconPack.tintable()) {
// Retrieve the textColor to tint the icon
val ta = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
ImageViewCompat.setImageTintList(iconImageView, ColorStateList.valueOf(ta.getColor(0, Color.BLACK)))
ta?.recycle()
}
}
return currentView
}
}
interface IconPickerListener {
fun iconPicked(bundle: Bundle)
}
companion object {
private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD"
fun getIconStandardFromBundle(bundle: Bundle): IconImageStandard? {
return bundle.getParcelable(KEY_ICON_STANDARD)
}
fun launch(activity: StylishActivity) {
// Create an instance of the dialog fragment and show it
val dialog = IconPickerDialogFragment()
dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment")
}
}
}

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.activities.dialogs
import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class PasswordEncodingDialogFragment : DialogFragment() {
var positiveButtonClickListener: DialogInterface.OnClickListener? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
/**
* Custom Dialog that asks the user to download the pro version or make a donation.
*/
class ProFeatureDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
}
} else {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.contribution_url)
}
}
builder.setMessage(stringBuilder)
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

View File

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

View File

@@ -0,0 +1,196 @@
/*
* 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.annotation.IdRes
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.widget.CompoundButton
import android.widget.RadioGroup
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.SortNodeEnum
class SortDialogFragment : DialogFragment() {
private var mListener: SortSelectionListener? = null
private var mSortNodeEnum: SortNodeEnum = SortNodeEnum.DB
@IdRes
private var mCheckedId: Int = 0
private var mGroupsBefore: Boolean = true
private var mAscending: Boolean = true
private var mRecycleBinBottom: Boolean = true
private var recycleBinBottomView: CompoundButton? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as SortSelectionListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + SortSelectionListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
var recycleBinAllowed = false
arguments?.apply {
if (containsKey(SORT_NODE_ENUM_BUNDLE_KEY))
getString(SORT_NODE_ENUM_BUNDLE_KEY)?.let {
mSortNodeEnum = SortNodeEnum.valueOf(it)
}
if (containsKey(SORT_ASCENDING_BUNDLE_KEY))
mAscending = getBoolean(SORT_ASCENDING_BUNDLE_KEY)
if (containsKey(SORT_GROUPS_BEFORE_BUNDLE_KEY))
mGroupsBefore = getBoolean(SORT_GROUPS_BEFORE_BUNDLE_KEY)
if (containsKey(SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY)) {
recycleBinAllowed = true
mRecycleBinBottom = getBoolean(SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY)
}
}
mCheckedId = retrieveViewFromEnum(mSortNodeEnum)
val rootView = activity.layoutInflater.inflate(R.layout.fragment_sort_selection, null)
builder.setTitle(R.string.sort_menu)
builder.setView(rootView)
// Add action buttons
.setPositiveButton(android.R.string.ok
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
// Check if is ascending or descending
ascendingView.isChecked = mAscending
ascendingView.setOnCheckedChangeListener { _, isChecked -> mAscending = isChecked }
val groupsBeforeView = rootView.findViewById<CompoundButton>(R.id.sort_selection_groups_before)
// Check if groups before
groupsBeforeView.isChecked = mGroupsBefore
groupsBeforeView.setOnCheckedChangeListener { _, isChecked -> mGroupsBefore = isChecked }
recycleBinBottomView = rootView.findViewById(R.id.sort_selection_recycle_bin_bottom)
if (!recycleBinAllowed) {
recycleBinBottomView?.visibility = View.GONE
} else {
// Check if recycle bin at the bottom
recycleBinBottomView?.isChecked = mRecycleBinBottom
recycleBinBottomView?.setOnCheckedChangeListener { _, isChecked -> mRecycleBinBottom = isChecked }
disableRecycleBinBottomOptionIfNaturalOrder()
}
val sortSelectionRadioGroupView = rootView.findViewById<RadioGroup>(R.id.sort_selection_radio_group)
// Check value by default
sortSelectionRadioGroupView.check(mCheckedId)
sortSelectionRadioGroupView.setOnCheckedChangeListener { _, checkedId ->
mSortNodeEnum = retrieveSortEnumFromViewId(checkedId)
disableRecycleBinBottomOptionIfNaturalOrder()
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun disableRecycleBinBottomOptionIfNaturalOrder() {
// Disable recycle bin if natural order
recycleBinBottomView?.isEnabled = mSortNodeEnum != SortNodeEnum.DB
}
@IdRes
private fun retrieveViewFromEnum(sortNodeEnum: SortNodeEnum): Int {
return when (sortNodeEnum) {
SortNodeEnum.DB -> R.id.sort_selection_db
SortNodeEnum.TITLE -> R.id.sort_selection_title
SortNodeEnum.USERNAME -> R.id.sort_selection_username
SortNodeEnum.CREATION_TIME -> R.id.sort_selection_creation_time
SortNodeEnum.LAST_MODIFY_TIME -> R.id.sort_selection_last_modify_time
SortNodeEnum.LAST_ACCESS_TIME -> R.id.sort_selection_last_access_time
}
}
private fun retrieveSortEnumFromViewId(@IdRes checkedId: Int): SortNodeEnum {
// Change enum
return when (checkedId) {
R.id.sort_selection_db -> SortNodeEnum.DB
R.id.sort_selection_title -> SortNodeEnum.TITLE
R.id.sort_selection_username -> SortNodeEnum.USERNAME
R.id.sort_selection_creation_time -> SortNodeEnum.CREATION_TIME
R.id.sort_selection_last_modify_time -> SortNodeEnum.LAST_MODIFY_TIME
R.id.sort_selection_last_access_time -> SortNodeEnum.LAST_ACCESS_TIME
else -> SortNodeEnum.TITLE
}
}
interface SortSelectionListener {
fun onSortSelected(sortNodeEnum: SortNodeEnum,
ascending: Boolean,
groupsBefore: Boolean,
recycleBinBottom: Boolean)
}
companion object {
private const val SORT_NODE_ENUM_BUNDLE_KEY = "SORT_NODE_ENUM_BUNDLE_KEY"
private const val SORT_ASCENDING_BUNDLE_KEY = "SORT_ASCENDING_BUNDLE_KEY"
private const val SORT_GROUPS_BEFORE_BUNDLE_KEY = "SORT_GROUPS_BEFORE_BUNDLE_KEY"
private const val SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY = "SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY"
private fun buildBundle(sortNodeEnum: SortNodeEnum,
ascending: Boolean,
groupsBefore: Boolean): Bundle {
val bundle = Bundle()
bundle.putString(SORT_NODE_ENUM_BUNDLE_KEY, sortNodeEnum.name)
bundle.putBoolean(SORT_ASCENDING_BUNDLE_KEY, ascending)
bundle.putBoolean(SORT_GROUPS_BEFORE_BUNDLE_KEY, groupsBefore)
return bundle
}
fun getInstance(sortNodeEnum: SortNodeEnum,
ascending: Boolean,
groupsBefore: Boolean): SortDialogFragment {
val bundle = buildBundle(sortNodeEnum, ascending, groupsBefore)
val fragment = SortDialogFragment()
fragment.arguments = bundle
return fragment
}
fun getInstance(sortNodeEnum: SortNodeEnum,
ascending: Boolean,
groupsBefore: Boolean,
recycleBinBottom: Boolean): SortDialogFragment {
val bundle = buildBundle(sortNodeEnum, ascending, groupsBefore)
bundle.putBoolean(SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY, recycleBinBottom)
val fragment = SortDialogFragment()
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.os.Build
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.text.Html
import android.text.SpannableStringBuilder
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.core.text.HtmlCompat
import com.kunzisoft.keepass.R
class UnavailableFeatureDialogFragment : DialogFragment() {
private var minVersionRequired = Build.VERSION_CODES.M
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
arguments?.apply {
if (containsKey(MIN_REQUIRED_VERSION_ARG))
minVersionRequired = getInt(MIN_REQUIRED_VERSION_ARG)
}
val rootView = activity.layoutInflater.inflate(R.layout.fragment_unavailable_feature, null)
val messageView = rootView.findViewById<TextView>(R.id.unavailable_feature_message)
val builder = AlertDialog.Builder(activity)
val message = SpannableStringBuilder()
message.append(getString(R.string.unavailable_feature_text))
.append("\n\n")
if (Build.VERSION.SDK_INT < minVersionRequired) {
message.append(getString(R.string.unavailable_feature_version,
androidNameFromApiNumber(Build.VERSION.SDK_INT, Build.VERSION.RELEASE),
androidNameFromApiNumber(minVersionRequired)))
message.append("\n\n")
.append(HtmlCompat.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>", HtmlCompat.FROM_HTML_MODE_LEGACY))
} else
message.append(getString(R.string.unavailable_feature_hardware))
messageView.text = message
messageView.movementMethod = LinkMovementMethod.getInstance()
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun androidNameFromApiNumber(apiNumber: Int, releaseVersion: String = ""): String {
var version = releaseVersion
val builder = StringBuilder()
val fields = Build.VERSION_CODES::class.java.fields
var apiName = ""
for (field in fields) {
val fieldName = field.name
var fieldValue = -1
try {
fieldValue = field.getInt(Any())
} catch (e: IllegalArgumentException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: NullPointerException) {
e.printStackTrace()
}
if (fieldValue == apiNumber) {
apiName = fieldName
}
}
if (apiName.isEmpty()) {
val mapper = arrayOf("ANDROID BASE", "ANDROID BASE 1.1", "CUPCAKE", "DONUT", "ECLAIR", "ECLAIR_0_1", "ECLAIR_MR1", "FROYO", "GINGERBREAD", "GINGERBREAD_MR1", "HONEYCOMB", "HONEYCOMB_MR1", "HONEYCOMB_MR2", "ICE_CREAM_SANDWICH", "ICE_CREAM_SANDWICH_MR1", "JELLY_BEAN", "JELLY_BEAN", "JELLY_BEAN", "KITKAT", "KITKAT", "LOLLIPOOP", "LOLLIPOOP_MR1", "MARSHMALLOW", "NOUGAT", "NOUGAT", "OREO", "OREO")
val index = apiNumber - 1
apiName = if (index < mapper.size) mapper[index] else "UNKNOWN_VERSION"
}
if (version.isEmpty()) {
val versions = arrayOf("1.0", "1.1", "1.5", "1.6", "2.0", "2.0.1", "2.1", "2.2.X", "2.3", "2.3.3", "3.0", "3.1", "3.2.0", "4.0.1", "4.0.3", "4.1.0", "4.2.0", "4.3.0", "4.4", "4.4", "5.0", "5.1", "6.0", "7.0", "7.1", "8.0.0", "8.1.0")
val index = apiNumber - 1
version = if (index < versions.size) versions[index] else "UNKNOWN_VERSION"
}
builder.append("\n\t")
if (apiName.isNotEmpty())
builder.append(apiName).append(" ")
if (version.isNotEmpty())
builder.append(version).append(" ")
builder.append("(API ").append(apiNumber).append(")")
builder.append("\n")
return builder.toString()
}
companion object {
private const val MIN_REQUIRED_VERSION_ARG = "MIN_REQUIRED_VERSION_ARG"
fun getInstance(minVersionRequired: Int): UnavailableFeatureDialogFragment {
val fragment = UnavailableFeatureDialogFragment()
val args = Bundle()
args.putInt(MIN_REQUIRED_VERSION_ARG, minVersionRequired)
fragment.arguments = args
return fragment
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
/**
* Custom Dialog that asks the user to download the pro version or make a donation.
*/
class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) {
if (BuildConfig.FULL_VERSION) {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
} else {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}
} else {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.contribution_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}
builder.setMessage(stringBuilder)
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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
import android.content.Context
import android.content.Intent
import android.os.Build
import com.kunzisoft.keepass.autofill.AutofillHelper
object EntrySelectionHelper {
private const val EXTRA_ENTRY_SELECTION_MODE = "com.kunzisoft.keepass.extra.ENTRY_SELECTION_MODE"
private const val DEFAULT_ENTRY_SELECTION_MODE = false
fun startActivityForEntrySelection(context: Context, intent: Intent) {
addEntrySelectionModeExtraInIntent(intent)
// only to avoid visible flickering when redirecting
context.startActivity(intent)
}
fun addEntrySelectionModeExtraInIntent(intent: Intent) {
intent.putExtra(EXTRA_ENTRY_SELECTION_MODE, true)
}
fun removeEntrySelectionModeFromIntent(intent: Intent) {
intent.removeExtra(EXTRA_ENTRY_SELECTION_MODE)
}
fun retrieveEntrySelectionModeFromIntent(intent: Intent): Boolean {
return intent.getBooleanExtra(EXTRA_ENTRY_SELECTION_MODE, DEFAULT_ENTRY_SELECTION_MODE)
}
fun doEntrySelectionAction(intent: Intent,
standardAction: () -> Unit,
keyboardAction: () -> Unit,
autofillAction: (assistStructure: AssistStructure) -> Unit) {
var assistStructureInit = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.retrieveAssistStructure(intent)?.let { assistStructure ->
autofillAction.invoke(assistStructure)
assistStructureInit = true
}
}
if (!assistStructureInit) {
if (intent.getBooleanExtra(EXTRA_ENTRY_SELECTION_MODE, DEFAULT_ENTRY_SELECTION_MODE)) {
intent.removeExtra(EXTRA_ENTRY_SELECTION_MODE)
keyboardAction.invoke()
} else {
standardAction.invoke()
}
}
}
}

View File

@@ -0,0 +1,225 @@
/*
* 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.annotation.SuppressLint
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.utils.UriUtil
class OpenFileHelper {
private var activity: Activity? = null
private var fragment: Fragment? = null
val openFileOnClickViewListener: OpenFileOnClickViewListener
get() = OpenFileOnClickViewListener()
constructor(context: Activity) {
this.activity = context
this.fragment = null
}
constructor(context: Fragment) {
this.activity = context.activity
this.fragment = context
}
inner class OpenFileOnClickViewListener : View.OnClickListener {
override fun onClick(v: View) {
try {
try {
openActivityWithActionOpenDocument()
} catch(e: Exception) {
openActivityWithActionGetContent()
}
} catch (e: Exception) {
Log.e(TAG, "Enable to start the file picker activity", e)
// Open browser dialog
if (lookForOpenIntentsFilePicker())
showBrowserDialog()
}
}
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionOpenDocument() {
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
else
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionGetContent() {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
else
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
}
private fun lookForOpenIntentsFilePicker(): Boolean {
var showBrowser = false
try {
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
if (fragment != null)
fragment?.startActivityForResult(intent, FILE_BROWSE)
else
activity?.startActivityForResult(intent, FILE_BROWSE)
} else {
showBrowser = true
}
} catch (e: Exception) {
Log.w(TAG, "Enable to start OPEN_INTENTS_FILE_BROWSE", e)
showBrowser = true
}
return showBrowser
}
/**
* Indicates whether the specified action can be used as an intent. This
* method queries the package manager for installed packages that can
* respond to an intent with the specified action. If no suitable package is
* found, this method returns false.
*
* @param context The application's environment.
* @param action The Intent action to check for availability.
*
* @return True if an Intent with the specified action can be sent and
* responded to, false otherwise.
*/
private fun isIntentAvailable(context: Context, action: String): Boolean {
val packageManager = context.packageManager
val intent = Intent(action)
val list = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY)
return list.size > 0
}
/**
* Show Browser dialog to select file picker app
*/
private fun showBrowserDialog() {
try {
val browserDialogFragment = BrowserDialogFragment()
if (fragment != null && fragment!!.fragmentManager != null)
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
else
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param keyFileCallback Callback retrieve from data
* @return true if requestCode was captured, false elsechere
*/
fun onActivityResultCallback(
requestCode: Int,
resultCode: Int,
data: Intent?,
keyFileCallback: ((uri: Uri?) -> Unit)?): Boolean {
when (requestCode) {
FILE_BROWSE -> {
if (resultCode == RESULT_OK) {
val filename = data?.dataString
var keyUri: Uri? = null
if (filename != null) {
keyUri = UriUtil.parse(filename)
}
keyFileCallback?.invoke(keyUri)
}
return true
}
GET_CONTENT, OPEN_DOC -> {
if (resultCode == RESULT_OK) {
if (data != null) {
val uri = data.data
if (uri != null) {
try {
// try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity?.contentResolver?.apply {
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
}
} catch (e: Exception) {
// nop
}
keyFileCallback?.invoke(uri)
}
}
}
return true
}
}
return false
}
companion object {
private const val TAG = "OpenFileHelper"
private var APP_ACTION_OPEN_DOCUMENT: String = try {
Intent::class.java.getField("ACTION_OPEN_DOCUMENT").get(null) as String
} catch (e: Exception) {
"android.intent.action.OPEN_DOCUMENT"
}
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
private const val GET_CONTENT = 25745
private const val OPEN_DOC = 25845
private const val FILE_BROWSE = 25645
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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
import android.content.Intent
import android.os.Bundle
import com.kunzisoft.keepass.settings.PreferencesUtil
object ReadOnlyHelper {
private const val READ_ONLY_KEY = "READ_ONLY_KEY"
const val READ_ONLY_DEFAULT = false
fun retrieveReadOnlyFromIntent(intent: Intent): Boolean {
return intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT)
}
fun retrieveReadOnlyFromInstanceStateOrPreference(context: Context, savedInstanceState: Bundle?): Boolean {
return if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
savedInstanceState.getBoolean(READ_ONLY_KEY)
} else {
PreferencesUtil.enableReadOnlyDatabase(context)
}
}
fun retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState: Bundle?, arguments: Bundle?): Boolean {
var readOnly = READ_ONLY_DEFAULT
if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY)
} else if (arguments != null && arguments.containsKey(READ_ONLY_KEY)) {
readOnly = arguments.getBoolean(READ_ONLY_KEY)
}
return readOnly
}
fun retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState: Bundle?, intent: Intent?): Boolean {
var readOnly = READ_ONLY_DEFAULT
if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY)
} else {
if (intent != null)
readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT)
}
return readOnly
}
fun putReadOnlyInIntent(intent: Intent, readOnly: Boolean) {
intent.putExtra(READ_ONLY_KEY, readOnly)
}
fun putReadOnlyInBundle(bundle: Bundle, readOnly: Boolean) {
bundle.putBoolean(READ_ONLY_KEY, readOnly)
}
fun onSaveInstanceState(outState: Bundle, readOnly: Boolean) {
outState.putBoolean(READ_ONLY_KEY, readOnly)
}
}

View File

@@ -0,0 +1,228 @@
/*
* 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.lock
import android.app.Activity
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.View
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
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.LOCK_ACTION
abstract class LockingActivity : StylishActivity() {
companion object {
private const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
}
protected var mTimeoutEnable: Boolean = true
private var mLockReceiver: LockReceiver? = null
private var mExitLock: Boolean = false
// Force readOnly if Entry Selection mode
protected var mReadOnly: Boolean = false
get() {
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)
if (savedInstanceState != null
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)) {
mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
} else {
if (intent != null)
mTimeoutEnable = intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
}
if (mTimeoutEnable) {
mLockReceiver = LockReceiver()
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF)
addAction(LOCK_ACTION)
}
registerReceiver(mLockReceiver, intentFilter)
}
mExitLock = false
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
mProgressDialogThread = ProgressDialogThread(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_EXIT_LOCK) {
mExitLock = true
if (Database.getInstance().loaded) {
lockAndExit()
}
}
}
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
if (!Database.getInstance().loaded) {
finish()
return
}
// After the first creation
// or If simply swipe with another application
// If the time is out -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
// If onCreate already record time
if (!mExitLock)
TimeoutHelper.recordTime(this)
}
}
override fun onSaveInstanceState(outState: Bundle) {
ReadOnlyHelper.onSaveInstanceState(outState, mReadOnly)
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
super.onSaveInstanceState(outState)
}
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
if (mTimeoutEnable) {
// If the time is out during our navigation in activity -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
}
}
override fun onDestroy() {
super.onDestroy()
if (mLockReceiver != null)
unregisterReceiver(mLockReceiver)
}
inner class LockReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// If allowed, lock and exit
if (!TimeoutHelper.temporarilyDisableTimeout) {
intent.action?.let {
when (it) {
Intent.ACTION_SCREEN_OFF -> if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this@LockingActivity)) {
lockAndExit()
}
LOCK_ACTION -> lockAndExit()
}
}
}
}
}
protected fun lockAndExit() {
lock()
}
/**
* To reset the app timeout when a view is focused or changed
*/
protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View?) {
views.forEach {
it?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
Log.d(TAG, "View focused, reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
}
}
if (it is ViewGroup) {
for (i in 0..it.childCount) {
resetAppTimeoutWhenViewFocusedOrChanged(it.getChildAt(i))
}
}
}
}
override fun onBackPressed() {
if (mTimeoutEnable) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
super.onBackPressed()
}
} else {
super.onBackPressed()
}
}
}
fun Activity.lock() {
// Stop the Magikeyboard service
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
MagikIME.removeEntry(this)
// Stop the notification service
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
" after inactivity or manual lock")
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
cancelAll()
}
// Clear data
Database.getInstance().closeAndClear(applicationContext.filesDir)
// Add onActivityForResult response
setResult(LockingActivity.RESULT_EXIT_LOCK)
finish()
}

View File

@@ -0,0 +1,72 @@
/*
* 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.stylish
import android.content.Context
import androidx.annotation.StyleRes
import androidx.preference.PreferenceManager
import android.util.Log
import com.kunzisoft.keepass.R
/**
* Class that provides functions to retrieve and assign a theme to a module
*/
object Stylish {
private var themeString: String? = null
/**
* Initialize the class with a theme preference
* @param context Context to retrieve the theme preference
*/
fun init(context: Context) {
val stylishPrefKey = context.getString(R.string.setting_style_key)
Log.d(Stylish::class.java.name, "Attatching to " + context.packageName)
themeString = PreferenceManager.getDefaultSharedPreferences(context).getString(stylishPrefKey, context.getString(R.string.list_style_name_light))
}
/**
* Assign the style to the class attribute
* @param styleString Style id String
*/
fun assignStyle(styleString: String) {
themeString = styleString
}
/**
* Function that returns the current id of the style selected in the preference
* @param context Context to retrieve the id
* @return Id of the style
*/
@StyleRes
fun getThemeId(context: Context): Int {
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
context.getString(R.string.list_style_name_purple) -> R.style.KeepassDXStyle_Purple
else -> R.style.KeepassDXStyle_Light
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.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() {
super.onResume()
if (Stylish.getThemeId(this) != this.themeId) {
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
this.recreate()
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.stylish
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import androidx.annotation.StyleRes
import androidx.fragment.app.Fragment
import androidx.appcompat.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
abstract class StylishFragment : Fragment() {
@StyleRes
protected var themeId: Int = 0
protected var contextThemed: Context? = null
override fun onAttach(context: Context) {
super.onAttach(context)
this.themeId = Stylish.getThemeId(context)
contextThemed = ContextThemeWrapper(context, themeId)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// To fix status bar color
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = activity!!.window
val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark)
val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark)
val defaultColor = Color.BLACK
window.statusBarColor = taColorPrimaryDark?.getColor(0, defaultColor) ?: defaultColor
taColorPrimaryDark?.recycle()
}
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onDetach() {
contextThemed = null
super.onDetach()
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
abstract class BasicViewHolder extends RecyclerView.ViewHolder {
View container;
ImageView icon;
TextView text;
TextView subText;
BasicViewHolder(View itemView) {
super(itemView);
}
}

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

@@ -0,0 +1,69 @@
/*
* 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.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
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<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))
}
override fun onBindViewHolder(holder: EntryHistoryViewHolder, position: Int) {
val entryHistory = entryHistoryList[position]
holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources)
holder.titleView.text = entryHistory.title
holder.usernameView.text = entryHistory.username
holder.urlView.text = entryHistory.url
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryHistory, position)
}
}
override fun getItemCount(): Int {
return entryHistoryList.size
}
fun clear() {
entryHistoryList.clear()
}
inner class EntryHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var lastModifiedView: TextView = itemView.findViewById(R.id.entry_history_last_modified)
var titleView: TextView = itemView.findViewById(R.id.entry_history_title)
var usernameView: TextView = itemView.findViewById(R.id.entry_history_username)
var urlView: TextView = itemView.findViewById(R.id.entry_history_url)
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters;
import android.view.View;
import com.kunzisoft.keepass.R;
class EntryViewHolder extends BasicViewHolder {
EntryViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.entry_container);
icon = itemView.findViewById(R.id.entry_icon);
text = itemView.findViewById(R.id.entry_text);
subText = itemView.findViewById(R.id.entry_subtext);
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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 androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.Field
import java.util.ArrayList
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
return FieldViewHolder(view)
}
override fun onBindViewHolder(holder: FieldViewHolder, position: Int) {
val field = fields[position]
holder.name.text = field.name
holder.bind(field, onItemClickListener)
}
override fun getItemCount(): Int {
return fields.size
}
fun setFields(fieldsToAdd: List<Field>) {
fields.clear()
fields.addAll(fieldsToAdd)
}
fun clear() {
fields.clear()
}
interface OnItemClickListener {
fun onItemClick(item: Field)
}
inner class FieldViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var name: TextView = itemView.findViewById(R.id.keyboard_popup_field_item_name)
fun bind(item: Field, listener: OnItemClickListener?) {
itemView.setOnClickListener { listener?.onItemClick(item) }
}
}
}

View File

@@ -0,0 +1,185 @@
/*
* 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 androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import android.util.TypedValue
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import android.widget.ViewSwitcher
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryEntity
import com.kunzisoft.keepass.utils.FileDatabaseInfo
import com.kunzisoft.keepass.utils.UriUtil
class FileDatabaseHistoryAdapter(private val context: Context)
: RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var fileItemOpenListener: ((FileDatabaseHistoryEntity)->Unit)? = null
private var fileSelectClearListener: ((FileDatabaseHistoryEntity)->Boolean)? = null
private var saveAliasListener: ((FileDatabaseHistoryEntity)->Unit)? = null
private val listDatabaseFiles = ArrayList<FileDatabaseHistoryEntity>()
private var mExpandedPosition = -1
private var mPreviousExpandedPosition = -1
@ColorInt
private val defaultColor: Int
@ColorInt
private val warningColor: Int
init {
val typedValue = TypedValue()
val theme = context.theme
theme.resolveAttribute(R.attr.colorAccent, typedValue, true)
warningColor = typedValue.data
theme.resolveAttribute(android.R.attr.textColorHintInverse, typedValue, true)
defaultColor = typedValue.data
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileDatabaseHistoryViewHolder {
val view = inflater.inflate(R.layout.item_file_row, parent, false)
return FileDatabaseHistoryViewHolder(view)
}
override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
// Get info from position
val fileHistoryEntity = listDatabaseFiles[position]
val fileDatabaseInfo = FileDatabaseInfo(context, fileHistoryEntity.databaseUri)
// Click item to open file
if (fileItemOpenListener != null)
holder.fileContainer.setOnClickListener {
fileItemOpenListener?.invoke(fileHistoryEntity)
}
// File alias
holder.fileAlias.text = fileDatabaseInfo.retrieveDatabaseAlias(fileHistoryEntity.databaseAlias)
// File path
holder.filePath.text = UriUtil.decode(fileDatabaseInfo.fileUri?.toString())
holder.filePreciseInfoContainer.visibility = if (fileDatabaseInfo.found()) {
// Modification
holder.fileModification.text = fileDatabaseInfo.getModificationString()
// Size
holder.fileSize.text = fileDatabaseInfo.getSizeString()
View.VISIBLE
} else
View.GONE
// Click on information
val isExpanded = position == mExpandedPosition
//This line hides or shows the layout in question
holder.fileExpandContainer.visibility = if (isExpanded) View.VISIBLE else View.GONE
// Save alias modification
holder.fileAliasCloseButton.setOnClickListener {
// Change the alias
fileHistoryEntity.databaseAlias = holder.fileAliasEdit.text.toString()
saveAliasListener?.invoke(fileHistoryEntity)
// Finish save mode
holder.fileMainSwitcher.showPrevious()
// Refresh current position to show alias
notifyItemChanged(position)
}
// Open alias modification
holder.fileModifyButton.setOnClickListener {
holder.fileAliasEdit.setText(holder.fileAlias.text)
holder.fileMainSwitcher.showNext()
}
holder.fileDeleteButton.setOnClickListener {
fileSelectClearListener?.invoke(fileHistoryEntity)
}
if (isExpanded) {
mPreviousExpandedPosition = position
}
holder.fileInformation.setOnClickListener {
mExpandedPosition = if (isExpanded) -1 else position
// Notify change
if (mPreviousExpandedPosition < itemCount)
notifyItemChanged(mPreviousExpandedPosition)
notifyItemChanged(position)
}
// Refresh View / Close alias modification if not contains fileAlias
if (holder.fileMainSwitcher.currentView.findViewById<View>(R.id.file_alias)
!= holder.fileAlias)
holder.fileMainSwitcher.showPrevious()
}
override fun getItemCount(): Int {
return listDatabaseFiles.size
}
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
listDatabaseFiles.clear()
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
}
fun deleteDatabaseFileHistory(fileDatabaseHistoryToDelete: FileDatabaseHistoryEntity) {
listDatabaseFiles.remove(fileDatabaseHistoryToDelete)
}
fun setOnFileDatabaseHistoryOpenListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
this.fileItemOpenListener = listener
}
fun setOnFileDatabaseHistoryDeleteListener(listener : ((FileDatabaseHistoryEntity)->Boolean)?) {
this.fileSelectClearListener = listener
}
fun setOnSaveAliasListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
this.saveAliasListener = listener
}
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info)
var fileAlias: TextView = itemView.findViewById(R.id.file_alias)
var fileInformation: ImageView = itemView.findViewById(R.id.file_information)
var fileMainSwitcher: ViewSwitcher = itemView.findViewById(R.id.file_main_switcher)
var fileAliasEdit: EditText = itemView.findViewById(R.id.file_alias_edit)
var fileAliasCloseButton: ImageView = itemView.findViewById(R.id.file_alias_save)
var fileExpandContainer: ViewGroup = itemView.findViewById(R.id.file_expand_container)
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
var filePath: TextView = itemView.findViewById(R.id.file_path)
var filePreciseInfoContainer: ViewGroup = itemView.findViewById(R.id.file_precise_info_container)
var fileModification: TextView = itemView.findViewById(R.id.file_modification)
var fileSize: TextView = itemView.findViewById(R.id.file_size)
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters;
import android.view.View;
import com.kunzisoft.keepass.R;
class GroupViewHolder extends BasicViewHolder {
GroupViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.group_container);
icon = itemView.findViewById(R.id.group_icon);
text = itemView.findViewById(R.id.group_text);
subText = itemView.findViewById(R.id.group_subtext);
}
}

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