Compare commits

..

397 Commits
3.1.0 ... 3.3.3

Author SHA1 Message Date
J-Jamet
1d71de7031 Merge branch 'release/3.3.3' 2022-03-26 16:27:15 +01:00
J-Jamet
e3e10e7dfa Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-03-25 16:03:08 +01:00
Kunzisoft
cbf900004d Translated using Weblate (French)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-03-25 16:03:01 +01:00
J-Jamet
51b36dc460 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-03-25 16:02:20 +01:00
Hosted Weblate
9f8016afe2 Merge branch 'origin/develop' into Weblate. 2022-03-25 15:56:17 +01:00
abidin toumi
d5a36db50a Translated using Weblate (Arabic)
Currently translated at 77.0% (457 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-03-25 15:56:17 +01:00
J-Jamet
ecd458d8d0 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-03-25 15:52:56 +01:00
J-Jamet
9088297c41 Fix small bugs 2022-03-25 15:48:33 +01:00
J-Jamet
5282deb088 Tabs to show main and advanced content separately 2022-03-25 15:32:48 +01:00
J-Jamet
75d661f12b Add a warning to inform about KeyStore usage #1269 2022-03-25 13:09:08 +01:00
J-Jamet
83bc769d9e Upgrade CHANGELOG 2022-03-23 20:10:39 +01:00
J-Jamet
8324acadc8 Toast when trying to open another database 2022-03-23 18:31:20 +01:00
J-Jamet
6e42db41be Fix shared otpauth link if database not open #1274 2022-03-23 18:03:32 +01:00
J-Jamet
3917bfc9e6 Update CHANGELOG 2022-03-23 17:46:42 +01:00
J-Jamet
d11febb1ce Ellipsize attachment name #1253 2022-03-23 17:41:51 +01:00
J-Jamet
360eb6f9cc Fix URL color 2022-03-23 15:40:08 +01:00
J-Jamet
e4ac5d01d0 Fingerprint unlock no more by default 2022-03-23 15:01:07 +01:00
J-Jamet
6a51fc0668 Fix settings title 2022-03-23 14:18:26 +01:00
J-Jamet
ba03f07fbe Replace Libre UI logo when no biometric 2022-03-23 13:34:04 +01:00
J-Jamet
7bf7d63f64 Better UI when no biometric is available 2022-03-23 13:13:29 +01:00
J-Jamet
d3efaabc24 Rollback algorithm 2022-03-23 09:29:45 +01:00
J-Jamet
b4283ed98b Small improvement 2022-03-23 09:15:36 +01:00
J-Jamet
de407e4cf9 Remove unused package 2022-03-20 16:40:19 +01:00
J-Jamet
60ed3a9836 Upgrade to 3.3.3 2022-03-19 15:53:00 +01:00
Stephan Paternotte
699ccf13f0 Translated using Weblate (Dutch)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-03-18 06:55:47 +01:00
Pavel Borecki
ae88aa4e42 Translated using Weblate (Czech)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-03-18 06:55:47 +01:00
J-Jamet
bcce13b12f Merge tag '3.3.2' into develop
3.3.2
2022-03-15 20:21:53 +01:00
J-Jamet
4fd4f660a7 Merge branch 'release/3.3.2' 2022-03-15 20:21:45 +01:00
J-Jamet
518e59b33c Merge branch 'App_Store_Merge' into release/3.3.2 2022-03-15 18:56:44 +01:00
J-Jamet
165cdcc00d Upgrade to 3.3.2 2022-03-15 18:52:05 +01:00
J-Jamet
48c2115fdf Fix small bugs 2022-03-15 13:17:23 +01:00
Stephan Paternotte
d7d68ccdeb Translated using Weblate (Dutch)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-03-11 00:00:32 +01:00
abidin toumi
5cf6362db4 Translated using Weblate (Arabic)
Currently translated at 77.5% (460 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-03-10 17:56:43 +01:00
Matthaiks
4efcc48160 Translated using Weblate (Polish)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-03-10 17:56:43 +01:00
Raghav Kabra
383274ce0f Translated using Weblate (Hindi)
Currently translated at 25.1% (149 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hi/
2022-03-07 12:00:12 +01:00
abidin toumi
c9dec3a2f7 Translated using Weblate (Arabic)
Currently translated at 77.2% (458 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-03-07 12:00:11 +01:00
Wilker Santana da Silva
2d4bf2903b Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-03-07 12:00:11 +01:00
Nextross
2b88cfbda0 Translated using Weblate (Czech)
Currently translated at 98.6% (585 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-03-07 12:00:10 +01:00
Raghav Kabra
eb6ab7a156 Translated using Weblate (Hindi)
Currently translated at 23.9% (142 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hi/
2022-03-05 17:21:22 +01:00
J-Jamet
9bed7bf213 Remove Pro version in fastlane 2022-03-03 16:58:28 +01:00
J-Jamet
99c2796014 Change icons and remove pro version 2022-03-03 16:50:46 +01:00
J-Jamet
9ee9bf12ae Replace icons 2022-03-03 13:10:10 +01:00
SC
688cbe50f2 Translated using Weblate (Portuguese)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-03-02 19:55:26 +01:00
VfBFan
e0577d1628 Translated using Weblate (German)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-03-02 19:55:25 +01:00
J-Jamet
de70925f8a Upgrade Gradle 2022-03-02 16:46:12 +01:00
Stephan Paternotte
0f8dd17fde Translated using Weblate (Dutch)
Currently translated at 99.4% (590 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-02-27 22:57:39 +01:00
Óscar Fernández Díaz
4bc8a08606 Translated using Weblate (Spanish)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-02-27 19:54:08 +01:00
Retrial
cf34433186 Translated using Weblate (Greek)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-02-27 19:54:07 +01:00
J-Jamet
21113d6fc8 Merge tag '3.3.1' into develop
3.3.1
2022-02-26 18:17:07 +01:00
J-Jamet
d7decba3f5 Merge branch 'release/3.3.1' 2022-02-26 18:16:59 +01:00
Oğuz Ersen
671364f395 Translated using Weblate (Turkish)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-02-26 17:20:50 +01:00
Eric
d0072237e7 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-02-26 17:20:50 +01:00
Ihor Hordiichuk
d11c001e0d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-02-26 17:20:50 +01:00
solokot
a197453ce0 Translated using Weblate (Russian)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-02-26 17:20:49 +01:00
Matthaiks
d1586a8d80 Translated using Weblate (Polish)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-02-26 17:20:49 +01:00
Kunzisoft
6e959e415f Translated using Weblate (French)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-02-26 17:20:49 +01:00
J-Jamet
8a8e14701b Merge branch 'translations' into develop 2022-02-26 17:14:07 +01:00
J-Jamet
60bb144020 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-02-26 17:13:29 +01:00
J-Jamet
b561db3a67 Workaround to fill OTP token in multiple fields with Magikeyboard (long press) #1158 2022-02-26 15:19:06 +01:00
J-Jamet
b0e722acce Best autofill recognition #1250 2022-02-26 15:18:08 +01:00
J-Jamet
f79d32b22b Fix temp advanced unlocking #1245 2022-02-26 12:45:59 +01:00
Hosted Weblate
3579606d1e Merge branch 'origin/develop' into Weblate. 2022-02-26 12:31:54 +01:00
J-Jamet
b93c6266a6 Upgrade CHANGELOG 2022-02-26 12:30:28 +01:00
J-Jamet
ea9c530667 Fix "other" search filter 2022-02-26 12:29:07 +01:00
J-Jamet
6ea95c050a Fix filter string #1249 2022-02-26 12:14:04 +01:00
SC
5a99a28195 Translated using Weblate (Portuguese)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-02-25 20:55:58 +01:00
Vitor Henrique
0cd5351c07 Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.8% (585 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-02-25 20:55:58 +01:00
VfBFan
148bed801b Translated using Weblate (German)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-02-25 20:55:57 +01:00
Larry Day
87a1a3ba1b Translated using Weblate (Czech)
Currently translated at 96.7% (573 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-02-25 20:55:57 +01:00
J-Jamet
20d2c10bba Upgrade room to 2.4.2 2022-02-25 12:50:01 +01:00
J-Jamet
3eed5e395e Upgrade to 3.3.1
Fix Japanese keyboard in search #1248
2022-02-25 12:44:38 +01:00
Oğuz Ersen
870fbaa05c Translated using Weblate (Turkish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-02-22 01:54:58 +01:00
Eric
55868f68da Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-02-22 01:54:58 +01:00
Ihor Hordiichuk
e6f0fbeab5 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-02-22 01:54:58 +01:00
solokot
8a554349b5 Translated using Weblate (Russian)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-02-22 01:54:57 +01:00
Matthaiks
ab87d4e564 Translated using Weblate (Polish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-02-22 01:54:57 +01:00
Éfrit
8400a83b70 Translated using Weblate (French)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-02-22 01:54:57 +01:00
Jeffree Romero
5d2caa37a9 Translated using Weblate (Spanish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-02-22 01:54:57 +01:00
Retrial
a6b7cfc2d3 Translated using Weblate (Greek)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-02-22 01:54:56 +01:00
J-Jamet
74107b90bb Max binary byte as 10 MB to prevent OOM #256 2022-02-20 17:16:36 +01:00
Hosted Weblate
9f4a915d43 Merge branch 'origin/develop' into Weblate. 2022-02-19 11:29:30 +01:00
Ismael Mirabile
3f1f22e1c3 Translated using Weblate (German)
Currently translated at 99.6% (590 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-02-19 11:29:30 +01:00
J-Jamet
1d98eb2b95 Merge tag '3.3.0' into develop
3.3.0
2022-02-19 11:01:11 +01:00
J-Jamet
f1b56fed16 Merge branch 'release/3.3.0' 2022-02-19 11:01:02 +01:00
J-Jamet
23e75c56ed Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-02-19 10:35:52 +01:00
J-Jamet
eafc9cc1fa Fix merge in kdb database 2022-02-19 10:31:29 +01:00
J-Jamet
3e3dbb22df Upgrade to version code 102 2022-02-19 10:21:24 +01:00
J-Jamet
127b8ba0bd Smaller time to the back to app handler 2022-02-18 17:46:12 +01:00
J-Jamet
8a8e588a01 Fix save search info 2022-02-18 17:30:25 +01:00
J-Jamet
3c1b010821 Default https if scheme is null 2022-02-18 15:27:49 +01:00
J-Jamet
739c938576 Fix save and app instance in selection mode 2022-02-18 15:09:15 +01:00
J-Jamet
69fed0c347 Remove redundant label 2022-02-18 13:37:25 +01:00
J-Jamet
d040258296 Fix lock button in settings 2022-02-18 13:14:33 +01:00
J-Jamet
abee18839b Fix selection mode 2022-02-18 13:12:01 +01:00
J-Jamet
9c0eb4e27e Upgrade to version 3.3.0_beta04 2022-02-18 11:32:00 +01:00
J-Jamet
10598ef5e5 Fix education hints #1192 2022-02-18 10:31:24 +01:00
J-Jamet
fa09e2d21d Refactoring education 2022-02-18 09:39:29 +01:00
Oğuz Ersen
11ddfe3445 Translated using Weblate (Turkish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-02-17 20:55:25 +01:00
Eric
9abbc8876b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-02-17 20:55:24 +01:00
Ihor Hordiichuk
c5cf99e13d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-02-17 20:55:24 +01:00
solokot
82ac1c8f5e Translated using Weblate (Russian)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-02-17 20:55:24 +01:00
Matthaiks
837f8146e4 Translated using Weblate (Polish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-02-17 20:55:24 +01:00
Kunzisoft
9698c3f2ee Translated using Weblate (French)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-02-17 20:55:23 +01:00
Retrial
85290b8f9d Translated using Weblate (Greek)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-02-17 20:55:23 +01:00
J-Jamet
1ccf48d7a5 Simpler fragment main credential view 2022-02-16 19:40:47 +01:00
J-Jamet
2cbec9f2b6 Change version code -> 100 Happy Bithcode 2022-02-16 19:30:12 +01:00
J-Jamet
692be415fa Fix small string 2022-02-16 19:16:45 +01:00
J-Jamet
83d5218f92 Fix view init for timeout 2022-02-16 19:13:41 +01:00
J-Jamet
324b016324 Remove unused file 2022-02-16 19:09:38 +01:00
J-Jamet
005c9673f7 Fix history banner color 2022-02-16 18:55:04 +01:00
J-Jamet
b8762dc6e0 Upgrade CHANGELOG number 2022-02-16 13:31:56 +01:00
J-Jamet
93abff0768 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-02-16 13:19:49 +01:00
Hosted Weblate
ac1cfb43d8 Merge branch 'origin/develop' into Weblate. 2022-02-16 13:19:07 +01:00
Oğuz Ersen
30ec712782 Translated using Weblate (Turkish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-02-16 13:18:57 +01:00
Eric
bbd93fbc5b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-02-16 13:18:57 +01:00
Ihor Hordiichuk
c46cbaff4b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-02-16 13:18:56 +01:00
solokot
1e979b256a Translated using Weblate (Russian)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-02-16 13:18:56 +01:00
Vitor Henrique
50429b419f Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.6% (578 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-02-16 13:18:55 +01:00
Matthaiks
2c540be5a3 Translated using Weblate (Polish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-02-16 13:18:54 +01:00
random r
155d3d222f Translated using Weblate (Italian)
Currently translated at 99.8% (591 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-02-16 13:18:54 +01:00
Kunzisoft
3fc3bf1302 Translated using Weblate (French)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-02-16 13:18:53 +01:00
Óscar Fernández Díaz
f0fafbbc6e Translated using Weblate (Spanish)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-02-16 13:18:53 +01:00
Retrial
5f7d980d89 Translated using Weblate (Greek)
Currently translated at 100.0% (592 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-02-16 13:18:52 +01:00
C. Rüdinger
c3799ed7fe Translated using Weblate (German)
Currently translated at 99.4% (589 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-02-16 13:18:52 +01:00
J-Jamet
f7533af5d8 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-02-16 13:12:58 +01:00
J-Jamet
2c9504fb93 Upgrade strings 2022-02-16 13:12:26 +01:00
J-Jamet
c22b3f5335 Upgrade libs 2022-02-16 13:01:05 +01:00
J-Jamet
2aea7fd46d Upgrade to 3.3.0_beta02 2022-02-16 13:00:49 +01:00
J-Jamet
bb9dda0cf1 Fix warning 2022-02-16 12:07:48 +01:00
VfBFan
ced6f05e59 Translated using Weblate (German)
Currently translated at 99.4% (589 of 592 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-02-15 22:13:47 +01:00
J-Jamet
cc14b5d4d4 Fix bad folder modification #1239 2022-02-15 11:29:50 +01:00
J-Jamet
2922f7110d Merge branch 'develop' into release/3.3.0 2022-02-14 16:35:08 +01:00
J-Jamet
762a877c5e Fix compilation 2022-02-14 16:34:42 +01:00
J-Jamet
aace7a8260 Change version to 3.3.0_beta01 2022-02-14 16:30:31 +01:00
J-Jamet
ac91abcb45 Fix color in Switch 2022-02-14 16:24:19 +01:00
J-Jamet
5d6ccd84c9 Fix icon selection colors 2022-02-14 16:20:31 +01:00
J-Jamet
bd2f980f8e Add new theme "Reply" and fix colors 2022-02-14 16:00:27 +01:00
J-Jamet
69af330ba1 Fix nav header 2022-02-14 15:24:11 +01:00
J-Jamet
b7bc3bfa8f Fix color flickering 2022-02-14 14:10:31 +01:00
J-Jamet
678663ad66 Add "Simple" theme 2022-02-14 12:51:27 +01:00
Hosted Weblate
ec3aa14112 Merge branch 'origin/develop' into Weblate. 2022-02-14 10:58:41 +01:00
Milo Ivir
3cdd98dc1a Translated using Weblate (Croatian)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-02-14 10:58:41 +01:00
Vitor Henrique
fc3270efd2 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-02-14 10:58:40 +01:00
J-Jamet
0dd871d3b5 Harmonize "cannot" 2022-02-14 10:53:33 +01:00
J-Jamet
41108ff407 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-02-14 10:49:42 +01:00
J-Jamet
99de8cf220 Update CHANGELOG 2022-02-13 17:06:44 +01:00
J-Jamet
a60e887eb3 Fix custom data parcelable #1236 2022-02-13 17:03:36 +01:00
J-Jamet
5133cb5c8f Fix auto type 2022-02-13 15:52:30 +01:00
J-Jamet
1607d84c21 Fix entry properties #1236 2022-02-13 15:47:20 +01:00
J-Jamet
cc0a990af8 Fix TOKEN translation #1232 2022-02-13 15:08:38 +01:00
J-Jamet
fec5bdcfc8 Harmonisation of main credential views 2022-02-13 14:59:39 +01:00
J-Jamet
648514d150 Refactor Password in MainCredential 2022-02-13 14:36:45 +01:00
J-Jamet
37b9745b6b Add lock in drawer menu 2022-02-13 14:21:49 +01:00
J-Jamet
f858b1144c Merge develop and selectable text 2022-02-13 13:49:41 +01:00
J-Jamet
c7cd7b83fa Change navigation view 2022-02-12 19:34:48 +01:00
J-Jamet
3cef086b69 Fix merge and save copy 2022-02-12 19:17:32 +01:00
J-Jamet
ca51f591de Visual point to see if a database is modified 2022-02-12 18:49:38 +01:00
Óscar Fernández Díaz
da63404a84 Translated using Weblate (Spanish)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-02-12 17:56:13 +01:00
J-Jamet
de2160f992 Fix small bugs 2022-02-12 16:01:30 +01:00
J-Jamet
8b7bb36e66 Merge branch 'feature/Merge_from' into develop #1221 #1204 #840 2022-02-12 14:57:44 +01:00
J-Jamet
6ade91dafa Update CHANGELOG 2022-02-12 14:57:29 +01:00
J-Jamet
2ef66f3011 Add data modified after a merge 2022-02-12 14:50:54 +01:00
J-Jamet
5b2fd0bfbb Fix small issues 2022-02-12 14:44:47 +01:00
J-Jamet
ab392e2cf3 Show URI in drawer 2022-02-12 13:53:05 +01:00
J-Jamet
8eb922e93f Fix menu 2022-02-12 13:34:36 +01:00
J-Jamet
20900dce07 Merge branch 'develop' into feature/Merge_from 2022-02-12 13:27:41 +01:00
J-Jamet
c2705ff5d2 Fix small bugs 2022-02-12 13:27:22 +01:00
J-Jamet
8af70fa7b5 Fix merge and save copy in KDB database 2022-02-12 13:20:22 +01:00
J-Jamet
e7eb8099ac Revert code to merge same KDB database 2022-02-12 13:02:34 +01:00
J-Jamet
cedf2eafbb Revert "Remove unused code"
This reverts commit cea7f1c2d1.
2022-02-12 12:57:57 +01:00
J-Jamet
e7553c68a0 Merge attachment from KDB database 2022-02-12 12:57:26 +01:00
J-Jamet
cea7f1c2d1 Remove unused code 2022-02-12 12:40:34 +01:00
J-Jamet
45937f9c9c Do not import meta stream entries 2022-02-12 12:37:49 +01:00
J-Jamet
ee2a1ea924 Add merge for KDB database 2022-02-12 12:33:40 +01:00
J-Jamet
1851c205e9 Merge branch 'develop' into feature/Merge_from 2022-02-12 11:51:16 +01:00
J-Jamet
c4a3947cb3 Fix null pointer with cipher database entity 2022-02-12 11:51:05 +01:00
J-Jamet
d3e76bcf21 Ask for new credential with a merge #1221 2022-02-12 11:42:36 +01:00
J-Jamet
cf4e64d6c5 Merge branch 'develop' into feature/Merge_from 2022-02-12 00:29:19 +01:00
J-Jamet
4cac571f1b Refactoring password view to prepare hardware key #8 2022-02-12 00:24:58 +01:00
J-Jamet
445ed92ff7 Update open database methods 2022-02-11 20:58:23 +01:00
J-Jamet
6686ce15c1 Implementation fo "Merge from" and "Save a copy to" 2022-02-11 19:40:56 +01:00
J-Jamet
59f134e0cd Change icons 2022-02-11 18:33:22 +01:00
J-Jamet
aab3f8c56f Nav header as dedicated view 2022-02-11 15:53:21 +01:00
J-Jamet
17c26e2a96 Add navigation drawer 2022-02-11 15:24:59 +01:00
Ciki Momogi
aec9124ef5 Translated using Weblate (Indonesian)
Currently translated at 90.3% (526 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-02-10 19:54:43 +01:00
random r
2df1e9bc2e Translated using Weblate (Italian)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-02-08 20:12:00 +01:00
J-Jamet
6ceecbfd0f Upgrade libs 2022-02-08 18:33:49 +01:00
J-Jamet
460726cadb Upgrade gradle 2022-02-08 18:14:09 +01:00
J-Jamet
32baa7b9f1 Fix searchable group 2022-02-08 15:45:24 +01:00
J-Jamet
f44e648ccc Fix and update max search 2022-02-08 14:49:25 +01:00
J-Jamet
2ad385acb6 Fix search filters depending on database version 2022-02-08 14:11:24 +01:00
J-Jamet
ebaec2eaf0 Fix visible available features 2022-02-08 13:39:16 +01:00
J-Jamet
dac8c2c15d Fix database v1 available features 2022-02-08 13:25:56 +01:00
J-Jamet
60965491b0 Add background to simple button 2022-02-08 12:50:51 +01:00
SC
19a3faa85a Translated using Weblate (Portuguese)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-02-08 00:15:38 +01:00
SC
3779346eed Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-02-08 00:15:38 +01:00
Stephan Paternotte
185e263ddd Translated using Weblate (Dutch)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-02-08 00:15:38 +01:00
J-Jamet
8c0d984955 Fix expires padding 2022-02-07 20:15:12 +01:00
J-Jamet
8d1012eda0 Upgrade CHANGELOG 2022-02-07 19:37:00 +01:00
J-Jamet
03f147e3d0 Merge branch 'feature/Searchable_Auto_Type' into develop 2022-02-07 19:35:07 +01:00
J-Jamet
13ef5d640c Add searchable group #1006 #905 2022-02-07 19:34:47 +01:00
J-Jamet
2e8eed5afe Visually remove auto type 2022-02-07 17:25:21 +01:00
J-Jamet
54653a5bea Add searchable and Auto-type in Group 2022-02-07 17:02:39 +01:00
Gabriel Cardoso
e29082eba3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-02-05 18:57:49 +01:00
hokonch
b635e9bb0d Translated using Weblate (Japanese)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-02-05 18:57:48 +01:00
J-Jamet
179bfdc3d2 Update CHANGELOG 2022-02-05 16:55:29 +01:00
J-Jamet
631cb104cd Fix styles 2022-02-05 16:48:30 +01:00
J-Jamet
1aeaf6855e Change search filters positions 2022-02-05 15:30:55 +01:00
J-Jamet
6bac1ace30 Fix multiple calls in search view 2022-02-05 15:20:51 +01:00
J-Jamet
488bab7c4d Fix UUID search 2022-02-05 14:43:53 +01:00
J-Jamet
69fbfd7723 Fix tag filter 2022-02-05 14:34:10 +01:00
J-Jamet
7697713c44 Search with regular expression #175 2022-02-05 14:23:30 +01:00
J-Jamet
ee9b072f45 Fix filter selection 2022-02-05 13:38:42 +01:00
J-Jamet
741defd31e Keep search context #1141 2022-02-05 13:33:57 +01:00
J-Jamet
e259b37f74 Add green in divine theme 2022-02-04 22:09:18 +01:00
Jérémy JAMET
9a87de797c Update README 2022-02-04 21:52:29 +01:00
J-Jamet
5d44ba658e Move case sensitive filter 2022-02-03 20:02:19 +01:00
J-Jamet
00518b8231 Default activity animation 2022-02-03 19:58:16 +01:00
J-Jamet
6a3a99d2ac Fix orange color and FAB color 2022-02-03 19:28:16 +01:00
J-Jamet
819434968c Fix and simpler colors for divine theme 2022-02-03 19:12:40 +01:00
J-Jamet
9f55ca2fdb Fix status bar and navigation bar color 2022-02-03 18:25:30 +01:00
J-Jamet
7f69563edb Upgrade CHANGELOG and version to 3.3.0 2022-02-03 17:25:36 +01:00
J-Jamet
b5cf0f987e Merge branch 'feature/Search_Refactoring' into develop 2022-02-03 17:21:43 +01:00
J-Jamet
28ad0b39c3 Remove search view hint 2022-02-03 17:21:25 +01:00
J-Jamet
b4e9040d5c Code encapsulation 2022-02-03 17:17:16 +01:00
J-Jamet
18a6ff0aa5 Fix quick search 2022-02-03 16:06:30 +01:00
J-Jamet
66e8c25265 Show current group title 2022-02-03 15:45:07 +01:00
J-Jamet
aba1f2d35b Fix orientation change 2022-02-03 15:31:05 +01:00
J-Jamet
5f29bcea8f Fix orientation change 2022-02-03 15:12:06 +01:00
J-Jamet
fac6fd7926 Dynamic height of filters 2022-02-03 14:56:05 +01:00
J-Jamet
5f4ab201af Merge branch 'develop' into feature/Search_Refactoring 2022-02-02 20:25:54 +01:00
J-Jamet
9591d36f9d Fix small translation 2022-02-02 20:25:38 +01:00
J-Jamet
a38f34995d Manage search keyboard 2022-02-02 20:25:08 +01:00
J-Jamet
f72564e48d Fix node action 2022-02-02 19:39:50 +01:00
J-Jamet
39ae743c64 Reorder filters 2022-02-02 19:26:58 +01:00
J-Jamet
9cdb355878 Enable filters depending on database 2022-02-02 19:14:23 +01:00
J-Jamet
6bdabbc96b Refactor group elements 2022-02-02 19:02:00 +01:00
solokot
d9e7d5ff6f Translated using Weblate (Russian)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-02-02 18:38:07 +01:00
J-Jamet
9f41da7868 Reorganize code 2022-02-02 18:36:07 +01:00
J-Jamet
d4f7258ed1 Search in current group 2022-02-02 18:32:43 +01:00
J-Jamet
aa34d78052 Fix chip style 2022-02-02 17:30:10 +01:00
J-Jamet
72712e8e0e Merge branch 'develop' into feature/Search_Refactoring 2022-02-02 12:39:59 +01:00
Hosted Weblate
bf25054ef2 Merge branch 'origin/develop' into Weblate. 2022-02-02 11:48:44 +01:00
Stephan Paternotte
32efafb404 Translated using Weblate (Dutch)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-02-02 11:48:44 +01:00
J-Jamet
3748ba1afa Merge tag '3.2.0' into develop
3.2.0
2022-02-02 11:30:55 +01:00
J-Jamet
5baf91dc77 Merge branch 'release/3.2.0' 2022-02-02 11:30:47 +01:00
J-Jamet
a152a48402 Upgrade to 3.2.0 2022-02-01 17:10:04 +01:00
J-Jamet
5fb4c4c20c Fix dialog when no modification performed 2022-02-01 17:06:51 +01:00
J-Jamet
80cf4f05f8 Better unknown KDF exception 2022-02-01 16:03:54 +01:00
J-Jamet
ce8e532f61 Larger view for breadcrumb 2022-02-01 15:50:20 +01:00
Allan Nordhøy
7e04fcbb6c Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.8% (517 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2022-02-01 05:53:21 +01:00
J-Jamet
71c37d0b8b Fix orientation change 2022-01-31 22:09:12 +01:00
J-Jamet
518375e29a Fix add button listeners 2022-01-31 14:23:46 +01:00
J-Jamet
25c845c1c7 Change max entry search 2022-01-31 14:21:38 +01:00
J-Jamet
6a468b339f Add search filters view 2022-01-31 13:46:17 +01:00
J-Jamet
05a52dd482 Fix search empty string 2022-01-31 11:23:52 +01:00
J-Jamet
5f1413ea1f Fix collapse animation 2022-01-31 00:07:48 +01:00
J-Jamet
84e45482a4 Fix fade animation 2022-01-30 23:45:19 +01:00
J-Jamet
32bfdca562 Advance search expand animation 2022-01-30 23:26:47 +01:00
J-Jamet
547971545e Merge branch 'develop' into feature/Search_Refactoring 2022-01-30 22:33:56 +01:00
J-Jamet
c12b16faf9 Merge branch 'develop' into feature/Search_Refactoring 2022-01-30 22:33:14 +01:00
J-Jamet
858d6c8723 Upgrade to 3.2.0_beta4 2022-01-30 19:44:46 +01:00
J-Jamet
0f021fae9f Fix Kdbx4 tag in Kdbx3 database #1222 2022-01-30 19:34:59 +01:00
J-Jamet
5cb42264c5 First commit for new search 2022-01-30 18:37:06 +01:00
Salih Ail
c17af4098f Translated using Weblate (Arabic)
Currently translated at 75.6% (440 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-01-30 15:57:02 +01:00
VfBFan
e6c094b433 Translated using Weblate (German)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-01-30 15:57:02 +01:00
Oğuz Ersen
b15a86618b Translated using Weblate (Turkish)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-01-29 03:55:09 +01:00
Eric
2e66cee551 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-01-29 03:55:09 +01:00
Ihor Hordiichuk
c6e3d0125a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-01-29 03:55:09 +01:00
solokot
f60f7d32dc Translated using Weblate (Russian)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-01-29 03:55:08 +01:00
Matthaiks
dd7175d0c6 Translated using Weblate (Polish)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-01-29 03:55:08 +01:00
J-Jamet
ba0e87f32a Fix warning 2022-01-28 15:56:16 +01:00
J-Jamet
cebba5a09d Upgrade to 3.2.0 beta3 2022-01-28 15:49:59 +01:00
J-Jamet
7b0c31c641 Fix search #1219 2022-01-28 15:49:00 +01:00
Kunzisoft
487be74e83 Translated using Weblate (English)
Currently translated at 99.8% (581 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-01-28 15:46:34 +01:00
J-Jamet
3c7c88e0f5 Hot fix for search #1219 2022-01-28 12:01:32 +01:00
J-Jamet
e876e89b41 Upgrade version code 2022-01-28 10:09:45 +01:00
J-Jamet
065fd9632c Update string 2022-01-27 18:56:13 +01:00
J-Jamet
f7d8641e31 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-01-27 18:50:34 +01:00
J-Jamet
ed6456599b Update string 2022-01-27 18:47:48 +01:00
Hosted Weblate
649538846a Merge branch 'origin/develop' into Weblate. 2022-01-27 18:44:48 +01:00
Kunzisoft
7468db5269 Translated using Weblate (French)
Currently translated at 99.6% (580 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-01-27 18:44:47 +01:00
Retrial
8291e94de4 Translated using Weblate (Greek)
Currently translated at 100.0% (582 of 582 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-01-27 18:44:47 +01:00
J-Jamet
94d30fc7ec Change string 2022-01-27 18:44:06 +01:00
Hosted Weblate
36c6f371c5 Merge branch 'origin/develop' into Weblate. 2022-01-27 18:32:27 +01:00
Balázs Meskó
94b3f66e14 Translated using Weblate (Hungarian)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2022-01-27 18:32:27 +01:00
J-Jamet
c9527ddacd Update CHANGELOG 2022-01-27 18:31:20 +01:00
J-Jamet
805c728e92 Change "show" strings by "displays" 2022-01-27 18:29:00 +01:00
J-Jamet
7812a86adb Add entry colors setting 2022-01-27 18:26:01 +01:00
J-Jamet
d22defcd83 Move settings position 2022-01-27 18:06:38 +01:00
J-Jamet
d06829ff7b Fix empty tag 2022-01-27 17:33:05 +01:00
J-Jamet
9110a1aca6 Fix checkboxes position with an error 2022-01-27 16:51:19 +01:00
J-Jamet
d7c90601d0 Add warning integrity message when keyfile is an image #1188 2022-01-27 16:47:47 +01:00
J-Jamet
7b5fc600f5 Merge branch 'feature/Tags' into develop 2022-01-27 16:12:01 +01:00
J-Jamet
0e699a1918 Add Tokenautocomplete from sources until https://github.com/splitwise/TokenAutoComplete/pull/422 fixed 2022-01-27 16:08:34 +01:00
J-Jamet
49f9964bc7 Add pool of tags 2022-01-27 15:37:37 +01:00
J-Jamet
ee3ec4b14d Add tags to group 2022-01-26 19:40:28 +01:00
J-Jamet
cdcc9f0aff Change tags views 2022-01-26 19:02:54 +01:00
J-Jamet
d56a01998f Merge branch 'develop' into feature/Tags 2022-01-26 18:43:21 +01:00
J-Jamet
4e7f7f7fa0 Fix lock in settings 2022-01-26 18:16:33 +01:00
J-Jamet
201d8f8aee Fix merge menu 2022-01-26 17:47:40 +01:00
J-Jamet
3847cb4d2d Merge branch 'feature/Keyboard_Refactoring' into develop 2022-01-26 15:12:01 +01:00
J-Jamet
89a338ac33 Suppress deprecation 2022-01-26 15:11:11 +01:00
J-Jamet
0084d113d3 Remove unused deprecated attribute 2022-01-26 15:10:21 +01:00
J-Jamet
6d49cc3577 Remove unused deprecated attribute 2022-01-26 15:10:06 +01:00
J-Jamet
910c45a6be Fix key press 2022-01-26 14:23:45 +01:00
J-Jamet
1d16ad764f First KeyboardView implementation 2022-01-26 14:01:08 +01:00
J-Jamet
142021c849 Update CHANGELOG 2022-01-25 18:18:40 +01:00
J-Jamet
c87b9768b1 small changelog change 2022-01-25 18:11:05 +01:00
J-Jamet
fbc3c1a9a4 Merge branch 'feature/Keep_Screen_On' of git://github.com/SUPERYAO541/KeePassDX into SUPERYAO541-feature/Keep_Screen_On 2022-01-25 18:04:49 +01:00
J-Jamet
05d1656a9e Inherit colors and icon from template #1213 #1130 2022-01-25 17:59:06 +01:00
J-Jamet
98804db478 Better kdf engine implementation 2022-01-25 16:37:18 +01:00
J-Jamet
c90f18c45a Better cipher engine implementation 2022-01-25 16:11:40 +01:00
J-Jamet
553783bfce Revert encryption commit 3426b3cdeb to prevent crash 2022-01-25 14:51:27 +01:00
J-Jamet
fbf67f28f5 Upgrade database version after a modification 2022-01-25 14:22:26 +01:00
J-Jamet
b8005466cd Inherit color from template #1213 2022-01-25 12:41:44 +01:00
J-Jamet
82177a386e Add copyright 2022-01-25 11:25:38 +01:00
J-Jamet
176b276835 Upgrade CHANGELOG 2022-01-25 11:04:18 +01:00
J-Jamet
a4732194ef Better quick search implementation and add path 2022-01-25 10:59:39 +01:00
I. Musthafa
4a815daf4d Translated using Weblate (Indonesian)
Currently translated at 89.9% (518 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-01-25 06:53:13 +01:00
Victor Mihalache
8aa7804e8d Translated using Weblate (Romanian)
Currently translated at 69.6% (401 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2022-01-25 06:53:13 +01:00
Oğuz Ersen
3b7086627d Translated using Weblate (Turkish)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-01-25 06:53:12 +01:00
Eric
20fb4036d1 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-01-25 06:53:11 +01:00
Ihor Hordiichuk
3679ad3d70 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-01-25 06:53:11 +01:00
solokot
63f23c571f Translated using Weblate (Russian)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-01-25 06:53:11 +01:00
André Marcelo Alvarenga
7e96303c7d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-01-25 06:53:11 +01:00
Matthaiks
4f5879179e Translated using Weblate (Polish)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-01-25 06:53:10 +01:00
Stephan Paternotte
5a81f54a1b Translated using Weblate (Dutch)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-01-25 06:53:10 +01:00
Retrial
d8ccaf3578 Translated using Weblate (Greek)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-01-25 06:53:09 +01:00
C. Rüdinger
7d0848f22b Translated using Weblate (German)
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-01-25 06:53:09 +01:00
André Marcelo Alvarenga
b02cffab56 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (576 of 576 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-01-23 23:49:47 +01:00
J-Jamet
59b6deb7be Fix color during selection 2022-01-23 12:38:35 +01:00
J-Jamet
1b734051d5 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-01-23 11:30:37 +01:00
J-Jamet
037fe41961 Merge branch 'Nickoriginal-patch-1' into develop 2022-01-23 11:23:56 +01:00
Nickoriginal
7bf1263c04 Update strings.xml
Fix mistyping
2022-01-22 16:01:13 +02:00
random r
0e4afd4681 Translated using Weblate (Italian)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-01-22 14:52:47 +01:00
Balázs Meskó
21930021ed Translated using Weblate (Hungarian)
Currently translated at 88.3% (507 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2022-01-22 14:52:46 +01:00
Oğuz Ersen
748b5c54c9 Translated using Weblate (Turkish)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-01-20 00:01:00 +01:00
Eric
82d305008b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-01-20 00:01:00 +01:00
Ihor Hordiichuk
1f24955533 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-01-20 00:00:59 +01:00
solokot
ccfa28b895 Translated using Weblate (Russian)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-01-20 00:00:59 +01:00
Matthaiks
2e3562d87e Translated using Weblate (Polish)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-01-20 00:00:59 +01:00
Oliver Cervera
f1be109c15 Translated using Weblate (Italian)
Currently translated at 99.6% (572 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-01-20 00:00:59 +01:00
Balázs Meskó
b09bd39ad4 Translated using Weblate (Hungarian)
Currently translated at 83.2% (478 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2022-01-20 00:00:58 +01:00
Kunzisoft
d812c3d61b Translated using Weblate (French)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-01-20 00:00:58 +01:00
Óscar Fernández Díaz
0d7772a4c6 Translated using Weblate (Spanish)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-01-20 00:00:58 +01:00
Retrial
a5abf4d186 Translated using Weblate (Greek)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-01-20 00:00:57 +01:00
VfBFan
c938a13482 Translated using Weblate (German)
Currently translated at 100.0% (574 of 574 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-01-20 00:00:57 +01:00
J-Jamet
0bfc395986 Merge branch 'feature/Merge_Data' into develop #840 2022-01-19 20:40:47 +01:00
J-Jamet
60c536f444 Fix save database info 2022-01-19 20:33:54 +01:00
J-Jamet
8129e6e0c1 First code to merge KDB and add doc 2022-01-19 20:10:50 +01:00
J-Jamet
223a8e9a5e Fix concurrent modification exception 2022-01-19 19:12:44 +01:00
J-Jamet
0e21c75007 replace updateGroup by addGroupIndex 2022-01-19 18:28:21 +01:00
J-Jamet
6ca031e8fd Merge custom data 2022-01-19 16:33:15 +01:00
J-Jamet
f02e86fb50 Confirm reload dialog if local modification #1196 2022-01-19 15:11:17 +01:00
J-Jamet
b17f8244da Remove merge menu in read only mode 2022-01-18 14:41:12 +01:00
J-Jamet
4c02ce138b Remove merge with kdb database 2022-01-18 14:37:46 +01:00
J-Jamet
5eba208b00 Upgrade to 3.2.0 and update CHANGELOG 2022-01-18 13:09:28 +01:00
J-Jamet
2a4fbbfb35 Merge branch 'develop' into feature/Merge_Data 2022-01-18 13:05:07 +01:00
Hosted Weblate
5dd8bdcae6 Merge branch 'origin/develop' into Weblate. 2022-01-18 13:04:51 +01:00
I. Musthafa
c3e82eea5d Translated using Weblate (Indonesian)
Currently translated at 90.8% (519 of 571 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-01-18 13:04:50 +01:00
Milo Ivir
2cfb96be33 Translated using Weblate (Croatian)
Currently translated at 100.0% (571 of 571 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-01-18 13:04:49 +01:00
Stephan Paternotte
715eedfab1 Translated using Weblate (Dutch)
Currently translated at 100.0% (571 of 571 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-01-18 13:04:49 +01:00
J-Jamet
7f3a1c9a7d Merge tag '3.1' into develop
3.1
2022-01-18 12:44:57 +01:00
J-Jamet
25077a7b9a Fix history merge 2022-01-17 23:33:20 +01:00
J-Jamet
6971cd1a6b Start merge database v1 2022-01-17 22:35:27 +01:00
J-Jamet
1e43a65743 Do not close the database when a merge failed 2022-01-17 22:18:46 +01:00
J-Jamet
2f2cbf343e Fix object deletion 2022-01-17 22:07:32 +01:00
J-Jamet
3426b3cdeb Better algorithm implementation 2022-01-17 21:31:05 +01:00
J-Jamet
e1d4c172f4 Merge settings 2022-01-17 20:29:10 +01:00
J-Jamet
4c4a67afaf Merge icons 2022-01-17 17:23:33 +01:00
J-Jamet
d33f210940 Add TODO and copyright 2022-01-15 22:31:28 +01:00
J-Jamet
ead59bd410 Merge entry history 2022-01-15 21:37:24 +01:00
J-Jamet
f9445de71f Merge database metadata 2022-01-15 21:03:40 +01:00
J-Jamet
c26995779a Change strings 2022-01-15 20:31:41 +01:00
J-Jamet
c6277453a3 Better binary management 2022-01-15 20:31:31 +01:00
J-Jamet
0f87bdd5a3 Merge branch 'develop' into feature/Merge_Data 2022-01-14 18:52:55 +01:00
J-Jamet
a6d6c247a8 Merge branch 'develop' into feature/Merge_Data 2022-01-07 19:02:22 +01:00
J-Jamet
dd170aafee Merge branch 'develop' into feature/Merge_Data 2022-01-07 14:39:17 +01:00
J-Jamet
9ba769c53e Fix merge and clear 2022-01-04 21:50:35 +01:00
J-Jamet
8ff57e3004 Better database loader implementation 2022-01-04 21:00:23 +01:00
J-Jamet
5c75c6c7d3 First pass to manage deleted objects 2022-01-04 16:06:39 +01:00
J-Jamet
56efb20ffa Fix delete methods 2022-01-04 15:12:58 +01:00
J-Jamet
699578bb59 Better recycle bin implementation 2022-01-04 13:31:18 +01:00
J-Jamet
f74147070a Add TODO for merge code 2022-01-03 19:53:19 +01:00
J-Jamet
20f4ea93e4 Merge branch 'develop' into feature/Merge_Data 2022-01-03 19:52:23 +01:00
J-Jamet
c550e1de54 First merge implementation 2022-01-03 19:25:48 +01:00
SUPERYAO
c3542224ae Merge branch 'develop' into feature/Keep_Screen_On 2021-10-25 18:03:04 +08:00
J-Jamet
429faf44db Allow to delete tags 2021-10-22 15:23:44 +02:00
J-Jamet
69ad7979ae Manually add token tags 2021-10-17 13:40:49 +02:00
J-Jamet
e87caac723 Put tag if not empty 2021-10-13 16:45:41 +02:00
J-Jamet
494544a4c2 Fix tags view position 2021-10-08 17:44:47 +02:00
J-Jamet
94b6118bf7 Fix theme and add autocomplete view 2021-10-05 14:30:49 +02:00
J-Jamet
7d6d86c6ff Merge branch 'develop' into feature/Tags 2021-10-04 16:49:38 +02:00
J-Jamet
bedc327e65 Manage Tags #633 2021-09-28 19:12:34 +02:00
SUPERYAO
40a893a8c9 There is no need to clear the flag when the option is not enabled 2021-09-24 22:30:21 +08:00
SUPERYAO
37ebd30a4d Keep the screen on when watching the entry 2021-09-24 20:33:57 +08:00
391 changed files with 15509 additions and 16746 deletions

View File

@@ -1,3 +1,44 @@
KeePassDX(3.3.3)
* Fix shared otpauth link if database not open #1274
* Ellipsize attachment name #1253
* Add a warning to inform about KeyStore usage #1269
* Fingerprint unlock no more by default #1273
* Tabs to show main and advanced content separately
* Fix URL color
KeePassDX(3.3.2)
* Merge KeePassDX & KeePassDX Pro #1257
* Create new Contributor Pro app
KeePassDX(3.3.1)
* Fix Japanese keyboard in search #1248
* Better OOM management #256
* Fix filters #1249
* Fix temp advanced unlocking #1245
* Best autofill recognition #1250
* Workaround to fill OTP token in multiple fields with Magikeyboard (long press) #1158
KeePassDX(3.3.0)
* Quick search and dynamic filters #163 #462 #521
* Keep search context #1141
* Add searchable groups #905 #1006
* Search with regular expression #175
* Merge from file and save as copy #1221 #1204 #840
* Fix custom data #1236
* Fix education hints #1192
* Fix save and app instance in selection mode
* New UI and fix styles
* Add "Simple" and "Reply" themes
KeePassDX(3.2.0)
* Manage data merge #840 #977
* Manage Tags #633
* Inherit colors and icon from template #1213 #1130
* Entry colors setting #1207
* Setting to keep the screen on when watching the entry #1119
* Add path in quick search
* Small fixes
KeePassDX(3.1.0)
* Add breadcrumb
* Add path in search results #1148

View File

@@ -43,7 +43,7 @@ Optional visual styles are accessible after a contribution (and a congratulatory
* Add features by making a **[pull request](https://help.github.com/articles/about-pull-requests/)**.
* Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** KeePassDX to your language (on [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or by sending a [pull request](https://help.github.com/articles/about-pull-requests/)).
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
* **[Donate](https://www.keepassdx.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 KeePassDX.
## Download

View File

@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
android {
@@ -11,8 +12,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 31
versionCode = 92
versionName = "3.1.0"
versionCode = 105
versionName = "3.3.3"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -42,36 +43,30 @@ android {
dimension "version"
applicationIdSuffix = ".libre"
buildConfigField "String", "BUILD_VERSION", "\"libre\""
buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "false"
buildConfigField "String[]", "STYLES_DISABLED",
"{\"KeepassDXStyle_Red\"," +
"\"KeepassDXStyle_Red_Night\"," +
"\"KeepassDXStyle_Reply\"," +
"\"KeepassDXStyle_Reply_Night\"," +
"\"KeepassDXStyle_Purple\"," +
"\"KeepassDXStyle_Purple_Dark\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
}
pro {
dimension "version"
applicationIdSuffix = ".pro"
buildConfigField "String", "BUILD_VERSION", "\"pro\""
buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", "{}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIZiXvrQCzSV9LNI6-p7cjTKENZLHIrz_zaqZuQQ" ]
}
free {
dimension "version"
applicationIdSuffix = ".free"
buildConfigField "String", "BUILD_VERSION", "\"free\""
buildConfigField "boolean", "FULL_VERSION", "false"
buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED",
"{\"KeepassDXStyle_Blue\"," +
"{\"KeepassDXStyle_Simple\"," +
"\"KeepassDXStyle_Simple_Night\"," +
"\"KeepassDXStyle_Blue\"," +
"\"KeepassDXStyle_Blue_Night\"," +
"\"KeepassDXStyle_Red\"," +
"\"KeepassDXStyle_Red_Night\"," +
"\"KeepassDXStyle_Reply\"," +
"\"KeepassDXStyle_Reply_Night\"," +
"\"KeepassDXStyle_Purple\"," +
"\"KeepassDXStyle_Purple_Dark\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
@@ -81,7 +76,6 @@ android {
sourceSets {
libre.res.srcDir 'src/libre/res'
pro.res.srcDir 'src/pro/res'
free.res.srcDir 'src/free/res'
}
@@ -99,22 +93,26 @@ android {
}
}
def room_version = "2.4.1"
def room_version = "2.4.2"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "com.android.support:multidex:1.0.3"
implementation "androidx.appcompat:appcompat:$android_appcompat_version"
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'androidx.media:media:1.4.3'
implementation 'androidx.media:media:1.5.0'
// Lifecycle - LiveData - ViewModel - Coroutines
implementation "androidx.core:core-ktx:$android_core_version"
implementation 'androidx.fragment:fragment-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation "com.google.android.material:material:$android_material_version"
// Token auto complete
// From sources until https://github.com/splitwise/TokenAutoComplete/pull/422 fixed
// implementation "com.splitwise:tokenautocomplete:4.0.0-beta04"
// Database
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"

View File

@@ -1,61 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="108"
android:viewportHeight="108"
android:width="108dp"
android:height="108dp">
android:height="108dp"
android:viewportWidth="120"
android:viewportHeight="120">
<group
android:translateY="-332">
<group
android:translateY="332">
android:translateX="6"
android:translateY="8">
<path
android:pathData="M65.728516 32.791016L58.052734 35.904297 56.173828 48.380859 35.306641 69.267578 35.238281 73.759766 69.478516 108 108 108 108 70.810547 73.09375 35.904297 65.728516 32.791016Z"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:strokeMiterLimit="4" >
<aapt:attr name="android:fillColor">
<gradient
android:endColor="#0000"
android:endX="80"
android:endY="80"
android:startColor="#4e000000"
android:startX="0"
android:startY="0"
android:type="linear"/>
</aapt:attr>
</path>
android:fillColor="#24000000"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:fillColor="#24000000"
android:strokeWidth="1.99999297"
android:pathData="M63.9961,34.0059 C61.5643,34.096,59.2564,35.102,57.5352,36.8223 C53.7682,40.589,53.7682,46.6982,57.5352,50.4649 C61.3017,54.232,67.4073,54.232,71.1739,50.4649 C74.9409,46.6982,74.9409,40.589,71.1739,36.8223 C69.2766,34.9258,66.6768,33.9054,63.9962,34.0059 Z M68.1992,40.6954 C69.8278,40.6958,71.148,42.016,71.1484,43.6446 C71.148,45.2732,69.8278,46.5934,68.1992,46.5938 C66.5706,46.5934,65.2504,45.2732,65.25,43.6446 C65.2504,42.016,66.5706,40.6958,68.1992,40.6954 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
android:translateX="6"
android:translateY="6">
<path
android:pathData="M88.76953 339.91602L4.1718754 424.59766 4.0000004 436 15.400391 435.82813 27.240234 424 40 424l0 -12 12 0 0 -12.73438 34.01172 -33.97656A8 8 0 0 1 84 360a8 8 0 0 1 8 -8 8 8 0 0 1 5.296882 2.01367l2.787098 -2.7832 -11.31445 -11.31445z"
android:fillColor="#eaeaea"
android:strokeWidth="1"
android:strokeColor="#58000000" />
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:pathData="M4.0000004 340L4.1718754 351.40137 88.59863 435.82812 100 436 99.828122 424.59863 15.401367 340.17188Z"
android:fillColor="#81c784" />
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
<path
android:pathData="M81.39454 332.00195a27 27 0 0 0 -19.48634 7.90625 27 27 0 0 0 0 38.1836 27 27 0 0 0 38.1836 0 27 27 0 0 0 0 -38.1836 27 27 0 0 0 -18.69726 -7.90625zM92 352a8 8 0 0 1 8 8 8 8 0 0 1 -8 8 8 8 0 0 1 -8 -8 8 8 0 0 1 8 -8z"
android:fillColor="#eaeaea"
android:strokeWidth="1"
android:strokeColor="#58000000" />
</group>
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M63.9961,34.0059 C61.5643,34.096,59.2564,35.102,57.5352,36.8223 C53.7682,40.589,53.7682,46.6982,57.5352,50.4649 C61.3017,54.232,67.4073,54.232,71.1739,50.4649 C74.9409,46.6982,74.9409,40.589,71.1739,36.8223 C69.2766,34.9258,66.6768,33.9054,63.9962,34.0059 Z M68.1992,40.6954 C69.8278,40.6958,71.148,42.016,71.1484,43.6446 C71.148,45.2732,69.8278,46.5934,68.1992,46.5938 C66.5706,46.5934,65.2504,45.2732,65.25,43.6446 C65.2504,42.016,66.5706,40.6958,68.1992,40.6954 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="84"
android:viewportHeight="84">
<group
android:translateX="-12"
android:translateY="-12">
<path
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M63.9961,34.0059 C61.5643,34.096,59.2564,35.102,57.5352,36.8223 C53.7682,40.589,53.7682,46.6982,57.5352,50.4649 C61.3017,54.232,67.4073,54.232,71.1739,50.4649 C74.9409,46.6982,74.9409,40.589,71.1739,36.8223 C69.2766,34.9258,66.6768,33.9054,63.9962,34.0059 Z M68.1992,40.6954 C69.8278,40.6958,71.148,42.016,71.1484,43.6446 C71.148,45.2732,69.8278,46.5934,68.1992,46.5938 C66.5706,46.5934,65.2504,45.2732,65.25,43.6446 C65.2504,42.016,66.5706,40.6958,68.1992,40.6954 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,61 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="108"
android:viewportHeight="108"
android:width="108dp"
android:height="108dp">
android:height="108dp"
android:viewportWidth="120"
android:viewportHeight="120">
<group
android:translateY="-332">
<group
android:translateY="332">
android:translateX="6"
android:translateY="8">
<path
android:pathData="M65.728516 32.791016L58.052734 35.904297 56.173828 48.380859 35.306641 69.267578 35.238281 73.759766 69.478516 108 108 108 108 70.810547 73.09375 35.904297 65.728516 32.791016Z"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:strokeMiterLimit="4" >
<aapt:attr name="android:fillColor">
<gradient
android:endColor="#0000"
android:endX="80"
android:endY="80"
android:startColor="#4e000000"
android:startX="0"
android:startY="0"
android:type="linear"/>
</aapt:attr>
</path>
android:fillColor="#24000000"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:fillColor="#24000000"
android:strokeWidth="1.99999297"
android:pathData="M64.501,35.0576 C63.7095,35.0576,62.918,35.3613,62.3115,35.9678 L55.0127,43.2666 C53.7998,44.4795,53.7998,46.4306,55.0127,47.6436 L62.3115,54.9424 C63.5244,56.1553,65.4775,56.1553,66.6904,54.9424 L73.9873,47.6436 C75.2002,46.4307,75.2002,44.4796,73.9873,43.2666 L66.6904,35.9678 C66.0839,35.3613,65.2924,35.0576,64.5009,35.0576 Z M67.6729,42.6006 C69.3298,42.6006,70.6729,43.9437,70.6729,45.6006 C70.6729,47.2575,69.3298,48.6006,67.6729,48.6006 C66.016,48.6006,64.6729,47.2575,64.6729,45.6006 C64.6729,43.9437,66.016,42.6006,67.6729,42.6006 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
android:translateX="6"
android:translateY="6">
<path
android:pathData="M88.76953 339.91602L4.1718754 424.59766 4.0000004 436 15.400391 435.82813 27.240234 424 40 424l0 -12 12 0 0 -12.73438 34.01172 -33.97656A8 8 0 0 1 84 360a8 8 0 0 1 8 -8 8 8 0 0 1 5.296882 2.01367l2.787098 -2.7832 -11.31445 -11.31445z"
android:fillColor="#eaeaea"
android:strokeWidth="1"
android:strokeColor="#58000000"/>
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:pathData="M4.0000004 340L4.1718754 351.40137 88.59863 435.82812 100 436 99.828122 424.59863 15.401367 340.17188Z"
android:fillColor="#64b5f6" />
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
<path
android:pathData="M81.39454 332.00195a27 27 0 0 0 -19.48634 7.90625 27 27 0 0 0 0 38.1836 27 27 0 0 0 38.1836 0 27 27 0 0 0 0 -38.1836 27 27 0 0 0 -18.69726 -7.90625zM92 352a8 8 0 0 1 8 8 8 8 0 0 1 -8 8 8 8 0 0 1 -8 -8 8 8 0 0 1 8 -8z"
android:fillColor="#eaeaea"
android:strokeWidth="1"
android:strokeColor="#58000000" />
</group>
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M64.501,35.0576 C63.7095,35.0576,62.918,35.3613,62.3115,35.9678 L55.0127,43.2666 C53.7998,44.4795,53.7998,46.4306,55.0127,47.6436 L62.3115,54.9424 C63.5244,56.1553,65.4775,56.1553,66.6904,54.9424 L73.9873,47.6436 C75.2002,46.4307,75.2002,44.4796,73.9873,43.2666 L66.6904,35.9678 C66.0839,35.3613,65.2924,35.0576,64.5009,35.0576 Z M67.6729,42.6006 C69.3298,42.6006,70.6729,43.9437,70.6729,45.6006 C70.6729,47.2575,69.3298,48.6006,67.6729,48.6006 C66.016,48.6006,64.6729,47.2575,64.6729,45.6006 C64.6729,43.9437,66.016,42.6006,67.6729,42.6006 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="84"
android:viewportHeight="84">
<group
android:translateX="-12"
android:translateY="-12">
<path
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M64.501,35.0576 C63.7095,35.0576,62.918,35.3613,62.3115,35.9678 L55.0127,43.2666 C53.7998,44.4795,53.7998,46.4306,55.0127,47.6436 L62.3115,54.9424 C63.5244,56.1553,65.4775,56.1553,66.6904,54.9424 L73.9873,47.6436 C75.2002,46.4307,75.2002,44.4796,73.9873,43.2666 L66.6904,35.9678 C66.0839,35.3613,65.2924,35.0576,64.5009,35.0576 Z M67.6729,42.6006 C69.3298,42.6006,70.6729,43.9437,70.6729,45.6006 C70.6729,47.2575,69.3298,48.6006,67.6729,48.6006 C66.016,48.6006,64.6729,47.2575,64.6729,45.6006 C64.6729,43.9437,66.016,42.6006,67.6729,42.6006 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/green" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/green" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -40,7 +40,6 @@
<activity
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
android:theme="@style/KeepassDXStyle.SplashScreen"
android:label="@string/app_name"
android:launchMode="singleTop"
android:exported="true"
android:configChanges="keyboardHidden"
@@ -51,7 +50,7 @@
</intent-filter>
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.PasswordActivity"
android:name="com.kunzisoft.keepass.activities.MainCredentialActivity"
android:exported="true"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="adjustResize|stateUnchanged">

View File

@@ -36,17 +36,21 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.ColorUtils
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.tabs.TabLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.EntryFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TagsAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
@@ -63,7 +67,6 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.changeControlColor
@@ -80,6 +83,9 @@ class EntryActivity : DatabaseLockActivity() {
private var appBarLayout: AppBarLayout? = null
private var titleIconView: ImageView? = null
private var historyView: View? = null
private var tagsListView: RecyclerView? = null
private var entryContentTab: TabLayout? = null
private var tagsAdapter: TagsAdapter? = null
private var entryProgress: LinearProgressIndicator? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null
@@ -87,6 +93,8 @@ class EntryActivity : DatabaseLockActivity() {
private val mEntryViewModel: EntryViewModel by viewModels()
private val mEntryActivityEducation = EntryActivityEducation(this)
private var mMainEntryId: NodeId<UUID>? = null
private var mHistoryPosition: Int = -1
private var mEntryIsHistory: Boolean = false
@@ -126,6 +134,8 @@ class EntryActivity : DatabaseLockActivity() {
appBarLayout = findViewById(R.id.app_bar)
titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container)
tagsListView = findViewById(R.id.entry_tags_list_view)
entryContentTab = findViewById(R.id.entry_content_tab)
entryProgress = findViewById(R.id.entry_progress)
lockView = findViewById(R.id.lock_button)
loadingView = findViewById(R.id.loading)
@@ -148,6 +158,26 @@ class EntryActivity : DatabaseLockActivity() {
taColorPrimary.recycle()
taColorBackground.recycle()
// Init Tags adapter
tagsAdapter = TagsAdapter(this)
tagsListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = tagsAdapter
}
// Init content tab
entryContentTab?.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
mEntryViewModel.selectSection(EntryViewModel.EntrySection.
getEntrySectionByPosition(tab?.position ?: 0)
)
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}
})
// Get Entry from UUID
try {
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { mainEntryId ->
@@ -179,6 +209,10 @@ class EntryActivity : DatabaseLockActivity() {
lockAndExit()
}
mEntryViewModel.sectionSelected.observe(this) { entrySection ->
entryContentTab?.getTabAt(entrySection.position)?.select()
}
mEntryViewModel.entryInfoHistory.observe(this) { entryInfoHistory ->
if (entryInfoHistory != null) {
this.mMainEntryId = entryInfoHistory.mainEntryId
@@ -213,9 +247,14 @@ class EntryActivity : DatabaseLockActivity() {
collapsingToolbarLayout?.title = entryTitle
toolbar?.title = entryTitle
mUrl = entryInfo.url
// Assign tags
val tags = entryInfo.tags
tagsListView?.visibility = if (tags.isEmpty()) View.GONE else View.VISIBLE
tagsAdapter?.setTags(tags)
// Assign colors
mBackgroundColor = entryInfo.backgroundColor
mForegroundColor = entryInfo.foregroundColor
val showEntryColors = PreferencesUtil.showEntryColors(this)
mBackgroundColor = if (showEntryColors) entryInfo.backgroundColor else null
mForegroundColor = if (showEntryColors) entryInfo.foregroundColor else null
loadingView?.hideByFading()
mEntryLoaded = true
@@ -312,6 +351,11 @@ class EntryActivity : DatabaseLockActivity() {
}
}
}
// Keep the screen on
if (PreferencesUtil.isKeepScreenOnEnabled(this)) {
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
override fun onPause() {
@@ -347,7 +391,6 @@ class EntryActivity : DatabaseLockActivity() {
super.onCreateOptionsMenu(menu)
if (mEntryLoaded) {
val inflater = menuInflater
MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu)
@@ -358,11 +401,7 @@ class EntryActivity : DatabaseLockActivity() {
// Show education views
Handler(Looper.getMainLooper()).post {
performedNextEducation(
EntryActivityEducation(
this
), menu
)
performedNextEducation(menu)
}
}
return true
@@ -374,40 +413,44 @@ class EntryActivity : DatabaseLockActivity() {
}
if (mEntryIsHistory || mDatabaseReadOnly) {
menu?.findItem(R.id.menu_save_database)?.isVisible = false
menu?.findItem(R.id.menu_merge_database)?.isVisible = false
menu?.findItem(R.id.menu_edit)?.isVisible = false
}
if (!mMergeDataAllowed) {
menu?.findItem(R.id.menu_merge_database)?.isVisible = false
}
if (mSpecialMode != SpecialMode.DEFAULT) {
menu?.findItem(R.id.menu_merge_database)?.isVisible = false
menu?.findItem(R.id.menu_reload_database)?.isVisible = false
}
applyToolbarColors()
return super.onPrepareOptionsMenu(menu)
}
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
menu: Menu) {
private fun performedNextEducation(menu: Menu) {
val entryFragment = supportFragmentManager.findFragmentByTag(ENTRY_FRAGMENT_TAG)
as? EntryFragment?
val entryFieldCopyView: View? = entryFragment?.firstEntryFieldCopyView()
val entryCopyEducationPerformed = entryFieldCopyView != null
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
&& mEntryActivityEducation.checkAndPerformedEntryCopyEducation(
entryFieldCopyView,
{
entryFragment.launchEntryCopyEducationAction()
},
{
performedNextEducation(entryActivityEducation, menu)
performedNextEducation(menu)
})
if (!entryCopyEducationPerformed) {
val menuEditView = toolbar?.findViewById<View>(R.id.menu_edit)
// entryEditEducationPerformed
menuEditView != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
menuEditView != null && mEntryActivityEducation.checkAndPerformedEntryEditEducation(
menuEditView,
{
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
},
{
performedNextEducation(entryActivityEducation, menu)
performedNextEducation(menu)
}
)
}
@@ -415,10 +458,6 @@ class EntryActivity : DatabaseLockActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
}
R.id.menu_edit -> {
mDatabase?.let { database ->
mMainEntryId?.let { entryId ->
@@ -455,6 +494,9 @@ class EntryActivity : DatabaseLockActivity() {
R.id.menu_save_database -> {
saveDatabase()
}
R.id.menu_merge_database -> {
mergeDatabase()
}
R.id.menu_reload_database -> {
reloadDatabase()
}

View File

@@ -113,7 +113,7 @@ class EntryEditActivity : DatabaseLockActivity(),
private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
// Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null
private var mEntryEditActivityEducation = EntryEditActivityEducation(this)
private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon ->
mEntryEditViewModel.selectIcon(icon)
@@ -183,8 +183,6 @@ class EntryEditActivity : DatabaseLockActivity(),
}
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
// Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this)
// Lock button
lockView?.setOnClickListener { lockAndExit() }
@@ -538,10 +536,8 @@ class EntryEditActivity : DatabaseLockActivity(),
super.onCreateOptionsMenu(menu)
if (mEntryLoaded) {
menuInflater.inflate(R.menu.entry_edit, menu)
entryEditActivityEducation?.let {
Handler(Looper.getMainLooper()).post {
performedNextEducation(it)
}
performedNextEducation()
}
}
return true
@@ -568,19 +564,19 @@ class EntryEditActivity : DatabaseLockActivity(),
return super.onPrepareOptionsMenu(menu)
}
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
private fun performedNextEducation() {
val entryEditFragment = supportFragmentManager.findFragmentById(R.id.entry_edit_content)
as? EntryEditFragment?
val generatePasswordView = entryEditFragment?.getActionImageView()
val generatePasswordEductionPerformed = generatePasswordView != null
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
&& mEntryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
generatePasswordView,
{
entryEditFragment.launchGeneratePasswordEductionAction()
},
{
performedNextEducation(entryEditActivityEducation)
performedNextEducation()
}
)
@@ -589,33 +585,33 @@ class EntryEditActivity : DatabaseLockActivity(),
val addNewFieldEducationPerformed = mAllowCustomFields
&& addNewFieldView != null
&& addNewFieldView.isVisible
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
&& mEntryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView,
{
addNewCustomField()
},
{
performedNextEducation(entryEditActivityEducation)
performedNextEducation()
}
)
if (!addNewFieldEducationPerformed) {
val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
val addAttachmentEducationPerformed = attachmentView != null
&& attachmentView.isVisible
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation(
&& mEntryEditActivityEducation.checkAndPerformedAttachmentEducation(
attachmentView,
{
addNewAttachment()
},
{
performedNextEducation(entryEditActivityEducation)
performedNextEducation()
}
)
if (!addAttachmentEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
setupOtpView != null
&& setupOtpView.isVisible
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
&& mEntryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
setupOtpView,
{
setupOtp()
@@ -662,8 +658,8 @@ class EntryEditActivity : DatabaseLockActivity(),
override fun acceptPassword(passwordField: Field) {
mEntryEditViewModel.selectPassword(passwordField)
entryEditActivityEducation?.let {
Handler(Looper.getMainLooper()).post { performedNextEducation(it) }
Handler(Looper.getMainLooper()).post {
performedNextEducation()
}
}

View File

@@ -165,15 +165,8 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
{
// If database not open
if (searchInfo.otpString != null) {
if (!readOnly) {
FileDatabaseSelectActivity.launchForSaveResult(this,
searchInfo)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (searchShareForMagikeyboard) {
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
searchInfo)

View File

@@ -31,18 +31,20 @@ import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
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.dialogs.SetMainCredentialDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
@@ -69,15 +71,18 @@ import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel
import java.io.FileNotFoundException
class FileDatabaseSelectActivity : DatabaseModeActivity(),
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
SetMainCredentialDialogFragment.AssignMainCredentialDialogListener {
// Views
private lateinit var coordinatorLayout: CoordinatorLayout
private var specialTitle: View? = null
private var createDatabaseButtonView: View? = null
private var openDatabaseButtonView: View? = null
private val databaseFilesViewModel: DatabaseFilesViewModel by viewModels()
private val mFileDatabaseSelectActivityEducation = FileDatabaseSelectActivityEducation(this)
// Adapter to manage database history list
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
@@ -110,6 +115,9 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
toolbar.title = ""
setSupportActionBar(toolbar)
// Special title
specialTitle = findViewById(R.id.file_selection_title_part_3)
// Create database button
createDatabaseButtonView = findViewById(R.id.create_database_button)
createDatabaseButtonView?.setOnClickListener { createNewFile() }
@@ -124,7 +132,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
mExternalFileHelper?.buildCreateDocument("application/x-keepass") { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
SetMainCredentialDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
} else {
val error = getString(R.string.error_create_database)
@@ -132,7 +140,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
Log.e(TAG, error)
}
}
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView = findViewById(R.id.open_database_button)
openDatabaseButtonView?.setOpenDocumentClickListener(mExternalFileHelper)
// History list
@@ -291,7 +299,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
}
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
PasswordActivity.launch(this,
MainCredentialActivity.launch(this,
databaseUri,
keyFile,
{ exception ->
@@ -333,6 +341,9 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
override fun onResume() {
super.onResume()
// Define special title
specialTitle?.isVisible = UriUtil.contributingUser(this)
// Show open and create button or special mode
when (mSpecialMode) {
SpecialMode.DEFAULT -> {
@@ -389,42 +400,43 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
super.onCreateOptionsMenu(menu)
if (mSpecialMode == SpecialMode.DEFAULT) {
MenuUtil.defaultMenuInflater(menuInflater, menu)
MenuUtil.defaultMenuInflater(this, menuInflater, menu)
}
Handler(Looper.getMainLooper()).post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
Handler(Looper.getMainLooper()).post {
performedNextEducation()
}
return true
}
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
private fun performedNextEducation() {
// If no recent files
val createDatabaseEducationPerformed =
createDatabaseButtonView != null
&& createDatabaseButtonView!!.visibility == View.VISIBLE
&& mAdapterDatabaseHistory != null
&& mAdapterDatabaseHistory!!.itemCount == 0
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
&& mFileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createDatabaseButtonView!!,
{
createNewFile()
},
{
// But if the user cancel, it can also select a database
performedNextEducation(fileDatabaseSelectActivityEducation)
performedNextEducation()
})
if (!createDatabaseEducationPerformed) {
// selectDatabaseEducationPerformed
openDatabaseButtonView != null
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
&& mFileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
openDatabaseButtonView!!,
{ tapTargetView ->
tapTargetView?.let {
mExternalFileHelper?.openDocument()
}
},
{}
)
{
})
}
}

View File

@@ -26,65 +26,66 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.*
import android.view.KeyEvent.KEYCODE_ENTER
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.InputMethodManager
import android.widget.*
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.CompoundButton
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.commit
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.*
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_DATABASE_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.MainCredentialView
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import java.io.FileNotFoundException
class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
// Views
private var toolbar: Toolbar? = null
private var filenameView: TextView? = null
private var passwordView: EditText? = null
private var keyFileSelectionView: KeyFileSelectionView? = null
private var advancedUnlockButton: View? = null
private var mainCredentialView: MainCredentialView? = null
private var confirmButtonView: Button? = null
private var checkboxPasswordView: CompoundButton? = null
private var checkboxKeyFileView: CompoundButton? = null
private var infoContainerView: ViewGroup? = null
private lateinit var coordinatorLayout: CoordinatorLayout
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
@@ -92,9 +93,10 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private val mDatabaseFileViewModel: DatabaseFileViewModel by viewModels()
private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by viewModels()
private val mPasswordActivityEducation = PasswordActivityEducation(this)
private var mDefaultDatabase: Boolean = false
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
private var mRememberKeyFile: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -110,7 +112,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_password)
setContentView(R.layout.activity_main_credential)
toolbar = findViewById(R.id.toolbar)
toolbar?.title = getString(R.string.app_name)
@@ -118,12 +120,10 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
confirmButtonView = findViewById(R.id.activity_password_open_button)
filenameView = findViewById(R.id.filename)
passwordView = findViewById(R.id.password)
keyFileSelectionView = findViewById(R.id.keyfile_selection)
checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
advancedUnlockButton = findViewById(R.id.activity_password_advanced_unlock_button)
mainCredentialView = findViewById(R.id.activity_password_credentials)
confirmButtonView = findViewById(R.id.activity_password_open_button)
infoContainerView = findViewById(R.id.activity_password_info_container)
coordinatorLayout = findViewById(R.id.activity_password_coordinator_layout)
@@ -134,43 +134,26 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
mExternalFileHelper = ExternalFileHelper(this@MainCredentialActivity)
mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) {
mDatabaseKeyFileUri = uri
populateKeyFileTextView(uri)
mainCredentialView?.populateKeyFileTextView(uri)
}
}
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
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
}
})
passwordView?.setOnKeyListener { _, _, keyEvent ->
var handled = false
if (keyEvent.action == KeyEvent.ACTION_DOWN
&& keyEvent?.keyCode == KEYCODE_ENTER) {
verifyCheckboxesAndLoadDatabase()
handled = true
}
handled
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
mainCredentialView?.onValidateListener = {
loadDatabase()
}
// If is a view intent
getUriFromIntent(intent)
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
}
// Init Biometric elements
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockButton?.setOnClickListener {
startActivity(Intent(this, SettingsAdvancedUnlockActivity::class.java))
}
}
advancedUnlockFragment = supportFragmentManager
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
if (advancedUnlockFragment == null) {
@@ -183,9 +166,10 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
// Listen password checkbox to init advanced unlock and confirmation button
checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
mainCredentialView?.onPasswordChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
mAdvancedUnlockViewModel.checkUnlockAvailability()
enableOrNotTheConfirmationButton()
enableConfirmationButton()
}
// Observe if default database
@@ -211,12 +195,13 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
invalidateOptionsMenu()
// Post init uri with KeyFile only if needed
val databaseKeyFileUri = mainCredentialView?.getMainCredential()?.keyFileUri
val keyFileUri =
if (mRememberKeyFile
&& (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
&& (databaseKeyFileUri == null || databaseKeyFileUri.toString().isEmpty())) {
databaseFile?.keyFileUri
} else {
mDatabaseKeyFileUri
databaseKeyFileUri
}
// Define title
@@ -229,10 +214,10 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
override fun onResume() {
super.onResume()
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@PasswordActivity)
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@MainCredentialActivity)
// Back to previous keyboard is setting activated
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@PasswordActivity)) {
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@MainCredentialActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
@@ -253,6 +238,15 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
if (database != null) {
// Trying to load another database
if (mDatabaseFileUri != null
&& database.fileUri != null
&& mDatabaseFileUri != database.fileUri) {
Toast.makeText(this,
R.string.warning_database_already_opened,
Toast.LENGTH_LONG
).show()
}
launchGroupActivityIfLoaded(database)
}
}
@@ -271,7 +265,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
if (result.isSuccess) {
launchGroupActivityIfLoaded(database)
} else {
passwordView?.requestFocusFromTouch()
mainCredentialView?.requestPasswordFocus()
var resultError = ""
val resultException = result.exception
@@ -288,7 +282,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
var databaseUri: Uri? = null
var mainCredential = MainCredential()
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
var cipherEncryptDatabase: CipherEncryptDatabase? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
@@ -296,8 +290,8 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
resultData.getParcelable(MAIN_CREDENTIAL_KEY)
?: mainCredential
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity =
resultData.getParcelable(CIPHER_ENTITY_KEY)
cipherEncryptDatabase =
resultData.getParcelable(CIPHER_DATABASE_KEY)
}
databaseUri?.let { databaseFileUri ->
@@ -305,7 +299,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
databaseFileUri,
mainCredential,
readOnly,
cipherEntity,
cipherEncryptDatabase,
true
)
}
@@ -341,11 +335,16 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
if (action != null
&& action == VIEW_INTENT) {
mDatabaseFileUri = intent.data
mDatabaseKeyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
mainCredentialView?.populateKeyFileTextView(UriUtil.getUriFromIntent(intent, KEY_KEYFILE))
} else {
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
intent?.getParcelableExtra<Uri?>(KEY_KEYFILE)?.let {
mainCredentialView?.populateKeyFileTextView(it)
}
}
try {
intent?.removeExtra(KEY_KEYFILE)
} catch (e: Exception) {}
mDatabaseFileUri?.let {
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
}
@@ -380,51 +379,68 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
finish()
}
override fun retrieveCredentialForEncryption(): String {
return passwordView?.text?.toString() ?: ""
override fun retrieveCredentialForEncryption(): ByteArray {
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
?: byteArrayOf()
}
override fun conditionToStoreCredential(): Boolean {
return checkboxPasswordView?.isChecked == true
return mainCredentialView?.conditionToStoreCredential() == true
}
override fun onCredentialEncrypted(databaseUri: Uri,
encryptedCredential: String,
ivSpec: String) {
override fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
// Load the database if password is registered with biometric
verifyCheckboxesAndLoadDatabase(
CipherDatabaseEntity(
databaseUri.toString(),
encryptedCredential,
ivSpec)
loadDatabase(mDatabaseFileUri,
mainCredentialView?.getMainCredential(),
cipherEncryptDatabase
)
}
override fun onCredentialDecrypted(databaseUri: Uri,
decryptedCredential: String) {
// Load the database if password is retrieve from biometric
// Retrieve from biometric
verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential)
private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener {
override fun passwordToStore(password: String?): ByteArray? {
return password?.toByteArray()
}
private val onEditorActionListener = object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == IME_ACTION_DONE) {
verifyCheckboxesAndLoadDatabase()
return true
override fun keyfileToStore(keyfile: Uri?): ByteArray? {
// TODO create byte array to store keyfile
return null
}
return false
override fun hardwareKeyToStore(): ByteArray? {
// TODO create byte array to store hardware key
return null
}
}
override fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
// Load the database if password is retrieve from biometric
// Retrieve from biometric
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()
when (cipherDecryptDatabase.credentialStorage) {
CredentialStorage.PASSWORD -> {
mainCredential.masterPassword = String(cipherDecryptDatabase.decryptedValue)
}
CredentialStorage.KEY_FILE -> {
// TODO advanced unlock key file
}
CredentialStorage.HARDWARE_KEY -> {
// TODO advanced unlock hardware key
}
}
loadDatabase(mDatabaseFileUri,
mainCredential,
null
)
}
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) {
// Define Key File text
if (mRememberKeyFile) {
populateKeyFileTextView(keyFileUri)
mainCredentialView?.populateKeyFileTextView(keyFileUri)
}
// Define listener for validate button
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
confirmButtonView?.setOnClickListener { loadDatabase() }
// If Activity is launch with a password and want to open directly
val intent = intent
@@ -433,66 +449,33 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
intent.removeExtra(KEY_PASSWORD)
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
if (password != null) {
populatePasswordTextView(password)
mainCredentialView?.populatePasswordTextView(password)
}
if (launchImmediately) {
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
loadDatabase()
} else {
// Init Biometric elements
mAdvancedUnlockViewModel.databaseFileLoaded(databaseFileUri)
}
enableOrNotTheConfirmationButton()
enableConfirmationButton()
// Auto select the password field and open keyboard
passwordView?.postDelayed({
passwordView?.requestFocusFromTouch()
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager?
inputMethodManager?.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT)
}, 100)
mainCredentialView?.focusPasswordFieldAndOpenKeyboard()
}
private fun enableOrNotTheConfirmationButton() {
private fun enableConfirmationButton() {
// 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)
}
if (!PreferencesUtil.emptyPasswordAllowed(this@MainCredentialActivity)) {
confirmButtonView?.isEnabled = mainCredentialView?.isFill() ?: false
} else {
confirmButtonView?.isEnabled = true
}
}
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
populatePasswordTextView(null)
mainCredentialView?.populatePasswordTextView(null)
if (clearKeyFile) {
mDatabaseKeyFileUri = null
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(uri: Uri?) {
if (uri == null || uri.toString().isEmpty()) {
keyFileSelectionView?.uri = null
if (checkboxKeyFileView?.isChecked == true)
checkboxKeyFileView?.isChecked = false
} else {
keyFileSelectionView?.uri = uri
if (checkboxKeyFileView?.isChecked != true)
checkboxKeyFileView?.isChecked = true
mainCredentialView?.populateKeyFileTextView(null)
}
}
@@ -504,41 +487,20 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
override fun onSaveInstanceState(outState: Bundle) {
mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString())
}
outState.putBoolean(KEY_READ_ONLY, mReadOnly)
super.onSaveInstanceState(outState)
}
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
val password: String? = passwordView?.text?.toString()
val keyFile: Uri? = keyFileSelectionView?.uri
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? = keyFileSelectionView?.uri
verifyKeyFileCheckbox(keyFile)
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
}
private fun verifyKeyFileCheckbox(keyFile: Uri?) {
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
private fun loadDatabase() {
loadDatabase(mDatabaseFileUri,
mainCredentialView?.getMainCredential(),
null
)
}
private fun loadDatabase(databaseFileUri: Uri?,
password: String?,
keyFileUri: Uri?,
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
mainCredential: MainCredential?,
cipherEncryptDatabase: CipherEncryptDatabase?) {
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
clearCredentialsViews()
@@ -557,10 +519,11 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
// Show the progress dialog and load the database
showProgressDialogAndLoadDatabase(
databaseUri,
MainCredential(password, keyFileUri),
mainCredential ?: MainCredential(),
mReadOnly,
cipherDatabaseEntity,
false)
cipherEncryptDatabase,
false
)
}
}
}
@@ -568,13 +531,13 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
cipherEncryptDatabase: CipherEncryptDatabase?,
fixDuplicateUUID: Boolean) {
loadDatabase(
databaseUri,
mainCredential,
readOnly,
cipherDatabaseEntity,
cipherEncryptDatabase,
fixDuplicateUUID
)
}
@@ -596,7 +559,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
if (mSpecialMode == SpecialMode.DEFAULT) {
MenuUtil.defaultMenuInflater(inflater, menu)
MenuUtil.defaultMenuInflater(this, inflater, menu)
}
super.onCreateOptionsMenu(menu)
@@ -612,26 +575,27 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler(Looper.getMainLooper()).post { performedNextEducation(PasswordActivityEducation(this), menu) }
Handler(Looper.getMainLooper()).post {
performedNextEducation(menu)
}
}
}
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu) {
private fun performedNextEducation(menu: Menu) {
val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
&& mPasswordActivityEducation.checkAndPerformedUnlockEducation(
educationToolbar,
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(menu)
})
if (!unlockEducationPerformed) {
val readOnlyEducationPerformed =
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
&& mPasswordActivityEducation.checkAndPerformedReadOnlyEducation(
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
{
try {
@@ -639,21 +603,30 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
} catch (e: Exception) {
Log.e(TAG, "Unable to find read mode menu")
}
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(menu)
})
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(this)
val biometricPerformed =
(biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockButton != null
&& mPasswordActivityEducation.checkAndPerformedBiometricEducation(
advancedUnlockButton!!,
{
startActivity(Intent(this, SettingsAdvancedUnlockActivity::class.java))
},
{
advancedUnlockFragment?.performEducation(passwordActivityEducation,
readOnlyEducationPerformed,
{
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu)
})
}
} catch (ignored: Exception) {}
}
}
private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
@@ -682,7 +655,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
companion object {
private val TAG = PasswordActivity::class.java.name
private val TAG = MainCredentialActivity::class.java.name
private const val UNLOCK_FRAGMENT_TAG = "UNLOCK_FRAGMENT_TAG"
@@ -696,7 +669,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java)
val intent = Intent(activity, MainCredentialActivity::class.java)
intent.putExtra(KEY_FILENAME, databaseFile)
if (keyFile != null)
intent.putExtra(KEY_KEYFILE, keyFile)
@@ -832,30 +805,30 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
try {
EntrySelectionHelper.doSpecialAction(activity.intent,
{
PasswordActivity.launch(activity,
MainCredentialActivity.launch(activity,
databaseUri, keyFile)
},
{ searchInfo -> // Search Action
PasswordActivity.launchForSearchResult(activity,
MainCredentialActivity.launchForSearchResult(activity,
databaseUri, keyFile,
searchInfo)
onLaunchActivitySpecialMode()
},
{ searchInfo -> // Save Action
PasswordActivity.launchForSaveResult(activity,
MainCredentialActivity.launchForSaveResult(activity,
databaseUri, keyFile,
searchInfo)
onLaunchActivitySpecialMode()
},
{ searchInfo -> // Keyboard Selection Action
PasswordActivity.launchForKeyboardResult(activity,
MainCredentialActivity.launchForKeyboardResult(activity,
databaseUri, keyFile,
searchInfo)
onLaunchActivitySpecialMode()
},
{ searchInfo, autofillComponent -> // Autofill Selection Action
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PasswordActivity.launchForAutofillResult(activity,
MainCredentialActivity.launchForAutofillResult(activity,
databaseUri, keyFile,
autofillActivityResultLauncher,
autofillComponent,
@@ -866,7 +839,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
},
{ registerInfo -> // Registration Action
PasswordActivity.launchForRegistration(activity,
MainCredentialActivity.launchForRegistration(activity,
databaseUri, keyFile,
registerInfo)
onLaunchActivitySpecialMode()

View File

@@ -27,7 +27,10 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.TagsAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.model.GroupInfo
@@ -43,11 +46,17 @@ class GroupDialogFragment : DatabaseDialogFragment() {
private lateinit var iconView: ImageView
private var mIconColor: Int = 0
private lateinit var nameTextView: TextView
private lateinit var tagsListView: RecyclerView
private var tagsAdapter: TagsAdapter? = null
private lateinit var notesTextLabelView: TextView
private lateinit var notesTextView: TextView
private lateinit var expirationView: DateTimeFieldView
private lateinit var creationView: TextView
private lateinit var modificationView: TextView
private lateinit var searchableLabelView: TextView
private lateinit var searchableView: TextView
private lateinit var autoTypeLabelView: TextView
private lateinit var autoTypeView: TextView
private lateinit var uuidContainerView: ViewGroup
private lateinit var uuidReferenceView: TextView
@@ -57,6 +66,25 @@ class GroupDialogFragment : DatabaseDialogFragment() {
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
}
mPopulateIconMethod?.invoke(iconView, mGroupInfo.icon)
if (database?.allowCustomSearchableGroup() == true) {
searchableLabelView.visibility = View.VISIBLE
searchableView.visibility = View.VISIBLE
} else {
searchableLabelView.visibility = View.GONE
searchableView.visibility = View.GONE
}
// TODO Auto-Type
/*
if (database?.allowAutoType() == true) {
autoTypeLabelView.visibility = View.VISIBLE
autoTypeView.visibility = View.VISIBLE
} else {
autoTypeLabelView.visibility = View.GONE
autoTypeView.visibility = View.GONE
}
*/
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -64,11 +92,16 @@ class GroupDialogFragment : DatabaseDialogFragment() {
val root = activity.layoutInflater.inflate(R.layout.fragment_group, null)
iconView = root.findViewById(R.id.group_icon)
nameTextView = root.findViewById(R.id.group_name)
tagsListView = root.findViewById(R.id.group_tags_list_view)
notesTextLabelView = root.findViewById(R.id.group_note_label)
notesTextView = root.findViewById(R.id.group_note)
expirationView = root.findViewById(R.id.group_expiration)
creationView = root.findViewById(R.id.group_created)
modificationView = root.findViewById(R.id.group_modified)
searchableLabelView = root.findViewById(R.id.group_searchable_label)
searchableView = root.findViewById(R.id.group_searchable)
autoTypeLabelView = root.findViewById(R.id.group_auto_type_label)
autoTypeView = root.findViewById(R.id.group_auto_type)
uuidContainerView = root.findViewById(R.id.group_UUID_container)
uuidReferenceView = root.findViewById(R.id.group_UUID_reference)
@@ -96,6 +129,14 @@ class GroupDialogFragment : DatabaseDialogFragment() {
nameTextView.text = title
nameTextView.visibility = View.VISIBLE
}
tagsAdapter = TagsAdapter(activity)
tagsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = tagsAdapter
}
val tags = mGroupInfo.tags
tagsListView.visibility = if (tags.isEmpty()) View.GONE else View.VISIBLE
tagsAdapter?.setTags(tags)
val notes = mGroupInfo.notes
if (notes == null || notes.isEmpty()) {
notesTextLabelView.visibility = View.GONE
@@ -109,6 +150,9 @@ class GroupDialogFragment : DatabaseDialogFragment() {
expirationView.dateTime = mGroupInfo.expiryTime
creationView.text = mGroupInfo.creationTime.getDateTimeString(resources)
modificationView.text = mGroupInfo.lastModificationTime.getDateTimeString(resources)
searchableView.text = stringFromInheritableBoolean(mGroupInfo.searchable)
autoTypeView.text = stringFromInheritableBoolean(mGroupInfo.enableAutoType,
mGroupInfo.defaultAutoTypeSequence)
val uuid = UuidUtil.toHexString(mGroupInfo.id)
if (uuid == null || uuid.isEmpty()) {
uuidContainerView.visibility = View.GONE
@@ -129,6 +173,15 @@ class GroupDialogFragment : DatabaseDialogFragment() {
return super.onCreateDialog(savedInstanceState)
}
private fun stringFromInheritableBoolean(enable: Boolean?, value: String? = null): String {
val valueString = if (value != null && value.isNotEmpty()) " [$value]" else ""
return when {
enable == null -> getString(R.string.inherited) + valueString
enable -> getString(R.string.enable) + valueString
else -> getString(R.string.disable)
}
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(KEY_GROUP_INFO, mGroupInfo)
super.onSaveInstanceState(outState)

View File

@@ -23,20 +23,23 @@ import android.app.Dialog
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.*
import com.kunzisoft.keepass.adapters.TagsProposalAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.view.DateTimeEditFieldView
import com.kunzisoft.keepass.view.InheritedCompletionView
import com.kunzisoft.keepass.view.TagsCompletionView
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
import com.tokenautocomplete.FilteredArrayAdapter
import org.joda.time.DateTime
class GroupEditDialogFragment : DatabaseDialogFragment() {
@@ -55,6 +58,14 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
private lateinit var notesTextLayoutView: TextInputLayout
private lateinit var notesTextView: TextView
private lateinit var expirationView: DateTimeEditFieldView
private lateinit var searchableContainerView: TextInputLayout
private lateinit var searchableView: InheritedCompletionView
private lateinit var autoTypeContainerView: ViewGroup
private lateinit var autoTypeInheritedView: InheritedCompletionView
private lateinit var autoTypeSequenceView: TextView
private lateinit var tagsContainerView: TextInputLayout
private lateinit var tagsCompletionView: TagsCompletionView
private var tagsAdapter: FilteredArrayAdapter<String>? = null
enum class EditGroupDialogAction {
CREATION, UPDATE, NONE;
@@ -107,10 +118,30 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mPopulateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
}
mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon)
searchableContainerView.visibility = if (database?.allowCustomSearchableGroup() == true) {
View.VISIBLE
} else {
View.GONE
}
if (database?.allowAutoType() == true) {
autoTypeContainerView.visibility = View.VISIBLE
} else {
autoTypeContainerView.visibility = View.GONE
}
tagsAdapter = TagsProposalAdapter(requireContext(), database?.tagPool)
tagsCompletionView.apply {
threshold = 1
setAdapter(tagsAdapter)
}
tagsContainerView.visibility = if (database?.allowTags() == true) View.VISIBLE else View.GONE
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -122,6 +153,13 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
notesTextLayoutView = root.findViewById(R.id.group_edit_note_container)
notesTextView = root.findViewById(R.id.group_edit_note)
expirationView = root.findViewById(R.id.group_edit_expiration)
searchableContainerView = root.findViewById(R.id.group_edit_searchable_container)
searchableView = root.findViewById(R.id.group_edit_searchable)
autoTypeContainerView = root.findViewById(R.id.group_edit_auto_type_container)
autoTypeInheritedView = root.findViewById(R.id.group_edit_auto_type_inherited)
autoTypeSequenceView = root.findViewById(R.id.group_edit_auto_type_sequence)
tagsContainerView = root.findViewById(R.id.group_tags_label)
tagsCompletionView = root.findViewById(R.id.group_tags_completion_view)
// Retrieve the textColor to tint the icon
val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
@@ -197,6 +235,19 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
}
expirationView.activation = groupInfo.expires
expirationView.dateTime = groupInfo.expiryTime
// Set searchable
searchableView.setValue(groupInfo.searchable)
// Set auto-type
autoTypeInheritedView.setValue(groupInfo.enableAutoType)
autoTypeSequenceView.text = groupInfo.defaultAutoTypeSequence
// Set Tags
groupInfo.tags.let { tags ->
tagsCompletionView.setText("")
for (i in 0 until tags.size()) {
tagsCompletionView.addObjectSync(tags.get(i))
}
}
}
private fun retrieveGroupInfoFromViews() {
@@ -208,6 +259,10 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
}
mGroupInfo.expires = expirationView.activation
mGroupInfo.expiryTime = expirationView.dateTime
mGroupInfo.searchable = searchableView.getValue()
mGroupInfo.enableAutoType = autoTypeInheritedView.getValue()
mGroupInfo.defaultAutoTypeSequence = autoTypeSequenceView.text.toString()
mGroupInfo.tags = tagsCompletionView.getTags()
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2022 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.net.Uri
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.MainCredentialView
class MainCredentialDialogFragment : DatabaseDialogFragment() {
private var mainCredentialView: MainCredentialView? = null
private var mListener: AskMainCredentialDialogListener? = null
private var mExternalFileHelper: ExternalFileHelper? = null
interface AskMainCredentialDialogListener {
fun onAskMainCredentialDialogPositiveClick(databaseUri: Uri?, mainCredential: MainCredential)
fun onAskMainCredentialDialogNegativeClick(databaseUri: Uri?, mainCredential: MainCredential)
}
override fun onAttach(activity: Context) {
super.onAttach(activity)
try {
mListener = activity as AskMainCredentialDialogListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString()
+ " must implement " + AskMainCredentialDialogListener::class.java.name)
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
var databaseUri: Uri? = null
arguments?.apply {
if (containsKey(KEY_ASK_CREDENTIAL_URI))
databaseUri = getParcelable(KEY_ASK_CREDENTIAL_URI)
}
val builder = AlertDialog.Builder(activity)
val root = activity.layoutInflater.inflate(R.layout.fragment_main_credential, null)
mainCredentialView = root.findViewById(R.id.main_credential_view)
databaseUri?.let {
root.findViewById<TextView>(R.id.title_database)?.text =
UriUtil.getFileData(requireContext(), it)?.name
}
builder.setView(root)
// Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.onAskMainCredentialDialogPositiveClick(
databaseUri,
retrieveMainCredential()
)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
mListener?.onAskMainCredentialDialogNegativeClick(
databaseUri,
retrieveMainCredential()
)
}
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) {
mainCredentialView?.populateKeyFileTextView(uri)
}
}
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun retrieveMainCredential(): MainCredential {
return mainCredentialView?.getMainCredential() ?: MainCredential()
}
companion object {
private const val KEY_ASK_CREDENTIAL_URI = "KEY_ASK_CREDENTIAL_URI"
const val TAG_ASK_MAIN_CREDENTIAL = "TAG_ASK_MAIN_CREDENTIAL"
fun getInstance(uri: Uri?): MainCredentialDialogFragment {
val fragment = MainCredentialDialogFragment()
val args = Bundle()
args.putParcelable(KEY_ASK_CREDENTIAL_URI, uri)
fragment.arguments = args
return fragment
}
}
}

View File

@@ -39,7 +39,7 @@ import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
private var mMasterPassword: String? = null
private var mKeyFile: Uri? = null
@@ -56,7 +56,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
private var keyFileCheckBox: CompoundButton? = null
private var keyFileSelectionView: KeyFileSelectionView? = null
private var mListener: AssignPasswordDialogListener? = null
private var mListener: AssignMainCredentialDialogListener? = null
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -74,7 +74,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
}
}
interface AssignPasswordDialogListener {
interface AssignMainCredentialDialogListener {
fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential)
fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential)
}
@@ -82,10 +82,10 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
override fun onAttach(activity: Context) {
super.onAttach(activity)
try {
mListener = activity as AssignPasswordDialogListener
mListener = activity as AssignMainCredentialDialogListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString()
+ " must implement " + AssignPasswordDialogListener::class.java.name)
+ " must implement " + AssignMainCredentialDialogListener::class.java.name)
}
}
@@ -112,7 +112,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
rootView = inflater.inflate(R.layout.fragment_set_password, null)
rootView = inflater.inflate(R.layout.fragment_set_main_credential, null)
builder.setView(rootView)
// Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ -> }
@@ -254,7 +254,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
.setPositiveButton(android.R.string.ok) { _, _ ->
if (!verifyKeyFile()) {
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
this@AssignMasterKeyDialogFragment.dismiss()
this@SetMainCredentialDialogFragment.dismiss()
}
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
@@ -269,7 +269,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
builder.setMessage(R.string.warning_no_encryption_key)
.setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
this@AssignMasterKeyDialogFragment.dismiss()
this@SetMainCredentialDialogFragment.dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
mNoKeyConfirmationDialog = builder.create()
@@ -301,8 +301,8 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"
fun getInstance(allowNoMasterKey: Boolean): AssignMasterKeyDialogFragment {
val fragment = AssignMasterKeyDialogFragment()
fun getInstance(allowNoMasterKey: Boolean): SetMainCredentialDialogFragment {
val fragment = SetMainCredentialDialogFragment()
val args = Bundle()
args.putBoolean(ALLOW_NO_MASTER_KEY_ARG, allowNoMasterKey)
fragment.arguments = args

View File

@@ -204,9 +204,10 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
android.R.layout.simple_spinner_item, mHotpTokenTypeArray!!).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
// Proprietary only on closed and full version
// Proprietary only on full version
mTotpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
UriUtil.contributingUser(activity)
)
totpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, mTotpTokenTypeArray!!).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)

View File

@@ -25,7 +25,6 @@ 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
@@ -40,22 +39,12 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
val builder = AlertDialog.Builder(activity)
val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) {
if (BuildConfig.FULL_VERSION) {
if (UriUtil.contributingUser(activity)) {
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(requireContext(), 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(" ")

View File

@@ -29,10 +29,12 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.adapters.TagsProposalAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.template.Template
@@ -40,11 +42,10 @@ import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.view.TemplateEditView
import com.kunzisoft.keepass.view.collapse
import com.kunzisoft.keepass.view.expand
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import com.tokenautocomplete.FilteredArrayAdapter
class EntryEditFragment: DatabaseFragment() {
@@ -55,6 +56,9 @@ class EntryEditFragment: DatabaseFragment() {
private lateinit var attachmentsContainerView: ViewGroup
private lateinit var attachmentsListView: RecyclerView
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
private lateinit var tagsContainerView: TextInputLayout
private lateinit var tagsCompletionView: TagsCompletionView
private var tagsAdapter: FilteredArrayAdapter<String>? = null
private var mTemplate: Template? = null
private var mAllowMultipleAttachments: Boolean = false
@@ -87,6 +91,8 @@ class EntryEditFragment: DatabaseFragment() {
templateView = view.findViewById(R.id.template_view)
attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
tagsContainerView = view.findViewById(R.id.entry_tags_label)
tagsCompletionView = view.findViewById(R.id.entry_tags_completion_view)
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsListView.apply {
@@ -146,18 +152,19 @@ class EntryEditFragment: DatabaseFragment() {
}
mEntryEditViewModel.requestEntryInfoUpdate.observe(viewLifecycleOwner) {
mEntryEditViewModel.saveEntryInfo(it.database, it.entry, it.parent, retrieveEntryInfo())
val entryInfo = retrieveEntryInfo()
mEntryEditViewModel.saveEntryInfo(it.database, it.entry, it.parent, entryInfo)
}
mEntryEditViewModel.onIconSelected.observe(viewLifecycleOwner) { iconImage ->
templateView.setIcon(iconImage)
}
mEntryEditViewModel.onBackgroundColorSelected.observe(this) { color ->
mEntryEditViewModel.onBackgroundColorSelected.observe(viewLifecycleOwner) { color ->
templateView.setBackgroundColor(color)
}
mEntryEditViewModel.onForegroundColorSelected.observe(this) { color ->
mEntryEditViewModel.onForegroundColorSelected.observe(viewLifecycleOwner) { color ->
templateView.setForegroundColor(color)
}
@@ -277,18 +284,34 @@ class EntryEditFragment: DatabaseFragment() {
attachmentsContainerView.expand(true)
}
}
tagsAdapter = TagsProposalAdapter(requireContext(), database?.tagPool)
tagsCompletionView.apply {
threshold = 1
setAdapter(tagsAdapter)
}
tagsContainerView.visibility = if (database?.allowTags() == true) View.VISIBLE else View.GONE
}
private fun assignEntryInfo(entryInfo: EntryInfo?) {
// Populate entry views
templateView.setEntryInfo(entryInfo)
// Set Tags
entryInfo?.tags?.let { tags ->
tagsCompletionView.setText("")
for (i in 0 until tags.size()) {
tagsCompletionView.addObjectSync(tags.get(i))
}
}
// Manage attachments
setAttachments(entryInfo?.attachments ?: listOf())
}
private fun retrieveEntryInfo(): EntryInfo {
val entryInfo = templateView.getEntryInfo()
entryInfo.tags = tagsCompletionView.getTags()
entryInfo.attachments = getAttachments().toMutableList()
return entryInfo
}

View File

@@ -16,7 +16,6 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo
@@ -25,6 +24,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.TemplateView
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.*
@@ -32,6 +32,9 @@ import java.util.*
class EntryFragment: DatabaseFragment() {
private lateinit var rootView: View
private lateinit var mainSection: View
private lateinit var advancedSection: View
private lateinit var templateView: TemplateView
private lateinit var creationDateView: TextView
@@ -41,6 +44,8 @@ class EntryFragment: DatabaseFragment() {
private lateinit var attachmentsListView: RecyclerView
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
private lateinit var customDataView: TextView
private lateinit var uuidContainerView: View
private lateinit var uuidReferenceView: TextView
@@ -70,6 +75,10 @@ class EntryFragment: DatabaseFragment() {
if (savedInstanceState == null) {
view.isVisible = false
}
mainSection = view.findViewById(R.id.entry_section_main)
advancedSection = view.findViewById(R.id.entry_section_advanced)
templateView = view.findViewById(R.id.entry_template)
loadTemplateSettings()
@@ -83,6 +92,9 @@ class EntryFragment: DatabaseFragment() {
creationDateView = view.findViewById(R.id.entry_created)
modificationDateView = view.findViewById(R.id.entry_modified)
// TODO Custom data
// customDataView = view.findViewById(R.id.entry_custom_data)
uuidContainerView = view.findViewById(R.id.entry_UUID_container)
uuidContainerView.apply {
visibility = if (PreferencesUtil.showUUID(context)) View.VISIBLE else View.GONE
@@ -106,6 +118,19 @@ class EntryFragment: DatabaseFragment() {
}
}
}
mEntryViewModel.sectionSelected.observe(viewLifecycleOwner) { entrySection ->
when (entrySection ?: EntryViewModel.EntrySection.MAIN) {
EntryViewModel.EntrySection.MAIN -> {
mainSection.showByFading()
advancedSection.hideByFading()
}
EntryViewModel.EntrySection.ADVANCED -> {
mainSection.hideByFading()
advancedSection.showByFading()
}
}
}
}
override fun onDatabaseRetrieved(database: Database?) {
@@ -154,11 +179,14 @@ class EntryFragment: DatabaseFragment() {
assignAttachments(entryInfo?.attachments ?: listOf())
// Assign dates
assignCreationDate(entryInfo?.creationTime)
assignModificationDate(entryInfo?.lastModificationTime)
creationDateView.text = entryInfo?.creationTime?.getDateTimeString(resources)
modificationDateView.text = entryInfo?.lastModificationTime?.getDateTimeString(resources)
// TODO Custom data
// customDataView.text = entryInfo?.customData?.toString()
// Assign special data
assignUUID(entryInfo?.id)
uuidReferenceView.text = UuidUtil.toHexString(entryInfo?.id)
}
private fun showClipboardDialog() {
@@ -189,18 +217,6 @@ class EntryFragment: DatabaseFragment() {
templateView.reload()
}
private fun assignCreationDate(date: DateInstant?) {
creationDateView.text = date?.getDateTimeString(resources)
}
private fun assignModificationDate(date: DateInstant?) {
modificationDateView.text = date?.getDateTimeString(resources)
}
private fun assignUUID(uuid: UUID?) {
uuidReferenceView.text = UuidUtil.toHexString(uuid)
}
/* -------------
* Attachments
* -------------

View File

@@ -152,7 +152,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
mAdapter = NodesAdapter(context, database).apply {
setOnNodeClickListener(object : NodesAdapter.NodeClickCallback {
override fun onNodeClick(database: Database, node: Node) {
if (nodeActionSelectionMode) {
if (mCurrentGroup?.isVirtual == false
&& nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
listActionNodes.remove(node)
@@ -169,7 +170,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
}
override fun onNodeLongClick(database: Database, node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
if (mCurrentGroup?.isVirtual == false
&& nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
listActionNodes.add(node)
@@ -257,9 +259,9 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
private fun rebuildList() {
try {
// Add elements to the list
mCurrentGroup?.let { mainGroup ->
mCurrentGroup?.let { currentGroup ->
// Thrown an exception when sort cannot be performed
mAdapter?.rebuildList(mainGroup)
mAdapter?.rebuildList(currentGroup)
}
} catch (e:Exception) {
Log.e(TAG, "Unable to rebuild the list", e)

View File

@@ -4,9 +4,9 @@ import android.net.Uri
import android.os.Bundle
import androidx.activity.viewModels
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
@@ -59,9 +59,9 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
fun loadDatabase(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
cipherEntity: CipherDatabaseEntity?,
cipherEncryptDatabase: CipherEncryptDatabase?,
fixDuplicateUuid: Boolean) {
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEntity, fixDuplicateUuid)
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEncryptDatabase, fixDuplicateUuid)
}
protected fun closeDatabase() {

View File

@@ -62,6 +62,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
private var mExitLock: Boolean = false
protected var mDatabaseReadOnly: Boolean = true
protected var mMergeDataAllowed: Boolean = false
private var mAutoSaveEnable: Boolean = true
protected var mIconDrawableFactory: IconDrawableFactory? = null
@@ -87,9 +88,15 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(save)
}
mDatabaseViewModel.mergeDatabase.observe(this) {
mDatabaseTaskProvider?.startDatabaseMerge()
}
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
mDatabaseTaskProvider?.askToStartDatabaseReload(mDatabase?.dataModifiedSinceLastLoading != false) {
mDatabaseTaskProvider?.startDatabaseReload(fixDuplicateUuid)
}
}
mDatabaseViewModel.saveName.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveName(it.oldValue, it.newValue, it.save)
@@ -197,6 +204,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
}
mDatabaseReadOnly = database.isReadOnly
mMergeDataAllowed = database.isMergeDataAllowed()
mIconDrawableFactory = database.iconDrawableFactory
checkRegister()
@@ -212,6 +220,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
DatabaseTaskNotificationService.ACTION_DATABASE_MERGE_TASK,
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
if (result.isSuccess) {
@@ -254,9 +263,23 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(true)
}
fun saveDatabaseTo(uri: Uri) {
mDatabaseTaskProvider?.startDatabaseSave(true, uri)
}
fun mergeDatabase() {
mDatabaseTaskProvider?.startDatabaseMerge()
}
fun mergeDatabaseFrom(uri: Uri, mainCredential: MainCredential) {
mDatabaseTaskProvider?.startDatabaseMerge(uri, mainCredential)
}
fun reloadDatabase() {
mDatabaseTaskProvider?.askToStartDatabaseReload(mDatabase?.dataModifiedSinceLastLoading != false) {
mDatabaseTaskProvider?.startDatabaseReload(false)
}
}
fun createEntry(newEntry: Entry,
parent: Group) {

View File

@@ -1,6 +1,8 @@
package com.kunzisoft.keepass.activities.legacy
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.Toast
import com.kunzisoft.keepass.R
@@ -11,6 +13,7 @@ import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.SpecialModeView
/**
* Activity to manage database special mode (ie: selection mode)
*/
@@ -63,8 +66,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
EntrySelectionHelper.removeModesFromIntent(intent)
EntrySelectionHelper.removeInfoFromIntent(intent)
if (mSpecialMode != SpecialMode.DEFAULT) {
// To move the app in background
moveTaskToBack(true)
backToTheMainAppAndFinish()
}
}
}
@@ -77,8 +79,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
EntrySelectionHelper.removeModesFromIntent(intent)
EntrySelectionHelper.removeInfoFromIntent(intent)
if (mSpecialMode != SpecialMode.DEFAULT) {
// To move the app in background
moveTaskToBack(true)
backToTheMainAppAndFinish()
}
}
}
@@ -88,11 +89,19 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
// To get the app caller, only for IntentSender
super.onBackPressed()
} else {
// To move the app in background
moveTaskToBack(true)
backToTheMainAppAndFinish()
}
}
private fun backToTheMainAppAndFinish() {
// To move the app in background and return to the main app
moveTaskToBack(true)
// To remove this instance in the OS app selector
Handler(Looper.getMainLooper()).postDelayed({
finish()
}, 500)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -160,12 +169,17 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
}
// To hide home button from the regular toolbar in special mode
if (mSpecialMode != SpecialMode.DEFAULT) {
if (mSpecialMode != SpecialMode.DEFAULT
&& hideHomeButtonIfModeIsNotDefault()) {
supportActionBar?.setDisplayHomeAsUpEnabled(false)
supportActionBar?.setDisplayShowHomeEnabled(false)
}
}
open fun hideHomeButtonIfModeIsNotDefault(): Boolean {
return true
}
private fun blockAutofill(searchInfo: SearchInfo?) {
val webDomain = searchInfo?.webDomain
val applicationId = searchInfo?.applicationId

View File

@@ -69,8 +69,10 @@ object Stylish {
context.getString(R.string.list_style_name_night) -> context.getString(R.string.list_style_name_light)
context.getString(R.string.list_style_name_black) -> context.getString(R.string.list_style_name_white)
context.getString(R.string.list_style_name_dark) -> context.getString(R.string.list_style_name_clear)
context.getString(R.string.list_style_name_simple_night) -> context.getString(R.string.list_style_name_simple)
context.getString(R.string.list_style_name_blue_night) -> context.getString(R.string.list_style_name_blue)
context.getString(R.string.list_style_name_red_night) -> context.getString(R.string.list_style_name_red)
context.getString(R.string.list_style_name_reply_night) -> context.getString(R.string.list_style_name_reply)
context.getString(R.string.list_style_name_purple_dark) -> context.getString(R.string.list_style_name_purple)
else -> styleString
}
@@ -81,8 +83,10 @@ object Stylish {
context.getString(R.string.list_style_name_light) -> context.getString(R.string.list_style_name_night)
context.getString(R.string.list_style_name_white) -> context.getString(R.string.list_style_name_black)
context.getString(R.string.list_style_name_clear) -> context.getString(R.string.list_style_name_dark)
context.getString(R.string.list_style_name_simple) -> context.getString(R.string.list_style_name_simple_night)
context.getString(R.string.list_style_name_blue) -> context.getString(R.string.list_style_name_blue_night)
context.getString(R.string.list_style_name_red) -> context.getString(R.string.list_style_name_red_night)
context.getString(R.string.list_style_name_reply) -> context.getString(R.string.list_style_name_reply_night)
context.getString(R.string.list_style_name_purple) -> context.getString(R.string.list_style_name_purple_dark)
else -> styleString
}
@@ -113,10 +117,14 @@ object Stylish {
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
context.getString(R.string.list_style_name_clear) -> R.style.KeepassDXStyle_Clear
context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark
context.getString(R.string.list_style_name_simple) -> R.style.KeepassDXStyle_Simple
context.getString(R.string.list_style_name_simple_night) -> R.style.KeepassDXStyle_Simple_Night
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
context.getString(R.string.list_style_name_blue_night) -> R.style.KeepassDXStyle_Blue_Night
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red
context.getString(R.string.list_style_name_red_night) -> R.style.KeepassDXStyle_Red_Night
context.getString(R.string.list_style_name_reply) -> R.style.KeepassDXStyle_Reply
context.getString(R.string.list_style_name_reply_night) -> R.style.KeepassDXStyle_Reply_Night
context.getString(R.string.list_style_name_purple) -> R.style.KeepassDXStyle_Purple
context.getString(R.string.list_style_name_purple_dark) -> R.style.KeepassDXStyle_Purple_Dark
else -> R.style.KeepassDXStyle_Light

View File

@@ -23,11 +23,13 @@ import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StyleRes
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
abstract class StylishFragment : Fragment() {
@@ -47,27 +49,41 @@ abstract class StylishFragment : Fragment() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = requireActivity().window
val defaultColor = Color.BLACK
val windowInset = WindowInsetsControllerCompat(window, window.decorView)
try {
val taStatusBarColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.statusBarColor))
window.statusBarColor = taStatusBarColor?.getColor(0, defaultColor) ?: defaultColor
taStatusBarColor?.recycle()
} catch (e: Exception) {}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : status bar color", e)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
val taWindowStatusLight = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar))
if (taWindowStatusLight?.getBoolean(0, false) == true) {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
windowInset.isAppearanceLightStatusBars = taWindowStatusLight
?.getBoolean(0, false) == true
taWindowStatusLight?.recycle()
} catch (e: Exception) {}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : window light status bar", e)
}
}
try {
val taNavigationBarColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.navigationBarColor))
window.navigationBarColor = taNavigationBarColor?.getColor(0, defaultColor) ?: defaultColor
taNavigationBarColor?.recycle()
} catch (e: Exception) {}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : navigation bar color", e)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
try {
val taWindowLightNavigationBar = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightNavigationBar))
windowInset.isAppearanceLightNavigationBars = taWindowLightNavigationBar
?.getBoolean(0, false) == true
taWindowLightNavigationBar?.recycle()
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : navigation light navigation bar", e)
}
}
}
return super.onCreateView(inflater, container, savedInstanceState)
}
@@ -76,4 +92,8 @@ abstract class StylishFragment : Fragment() {
contextThemed = null
super.onDetach()
}
companion object {
private val TAG = StylishFragment::class.java.simpleName
}
}

View File

@@ -26,11 +26,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
@@ -75,6 +73,7 @@ class NodesAdapter (private val context: Context,
private var mNumberChildrenTextDefaultDimension: Float = 0F
private var mIconDefaultDimension: Float = 0F
private var mShowEntryColors: Boolean = true
private var mShowUserNames: Boolean = true
private var mShowNumberEntries: Boolean = true
private var mShowOTP: Boolean = false
@@ -87,8 +86,6 @@ class NodesAdapter (private val context: Context,
private var mNodeClickCallback: NodeClickCallback? = null
private var mClipboardHelper = ClipboardHelper(context)
@ColorInt
private val mContentSelectionColor: Int
@ColorInt
private val mTextColorPrimary: Int
@ColorInt
@@ -98,7 +95,7 @@ class NodesAdapter (private val context: Context,
@ColorInt
private val mColorAccentLight: Int
@ColorInt
private val mTextColorInverse: Int
private val mColorOnAccentColor: Int
/**
* Determine if the adapter contains or not any element
@@ -115,8 +112,6 @@ class NodesAdapter (private val context: Context,
this.mNodeSortedListCallback = NodeSortedListCallback()
this.mNodeSortedList = SortedList(Node::class.java, mNodeSortedListCallback)
// Color of content selection
this.mContentSelectionColor = ContextCompat.getColor(context, R.color.white)
// Retrieve the color to tint the icon
val taTextColorPrimary = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary))
this.mTextColorPrimary = taTextColorPrimary.getColor(0, Color.BLACK)
@@ -130,13 +125,13 @@ class NodesAdapter (private val context: Context,
this.mTextColorSecondary = taTextColorSecondary.getColor(0, Color.BLACK)
taTextColorSecondary.recycle()
// To get background color for selection
val taSelectionColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccentLight))
this.mColorAccentLight = taSelectionColor.getColor(0, Color.GRAY)
taSelectionColor.recycle()
val taColorAccentLight = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccentLight))
this.mColorAccentLight = taColorAccentLight.getColor(0, Color.GRAY)
taColorAccentLight.recycle()
// To get text color for selection
val taSelectionTextColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
this.mTextColorInverse = taSelectionTextColor.getColor(0, Color.WHITE)
taSelectionTextColor.recycle()
val taColorOnAccentColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnAccentColor))
this.mColorOnAccentColor = taColorOnAccentColor.getColor(0, Color.WHITE)
taColorOnAccentColor.recycle()
}
private fun assignPreferences() {
@@ -151,6 +146,7 @@ class NodesAdapter (private val context: Context,
)
)
this.mShowEntryColors = PreferencesUtil.showEntryColors(context)
this.mShowUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.mShowNumberEntries = PreferencesUtil.showNumberEntries(context)
this.mShowOTP = PreferencesUtil.showOTPToken(context)
@@ -351,23 +347,6 @@ class NodesAdapter (private val context: Context,
isSelected = mActionNodesList.contains(subNode)
}
// Assign image
val iconColor = if (holder.container.isSelected)
mContentSelectionColor
else when (subNode.type) {
Type.GROUP -> mTextColorPrimary
Type.ENTRY -> mTextColor
}
holder.imageIdentifier?.setColorFilter(iconColor)
holder.icon.apply {
database.iconDrawableFactory.assignDatabaseIcon(this, subNode.icon, iconColor)
// Relative size of the icon
layoutParams?.apply {
height = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
width = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
}
}
// Assign text
holder.text.apply {
text = subNode.title
@@ -395,6 +374,14 @@ class NodesAdapter (private val context: Context,
holder.path?.visibility = View.GONE
}
// Assign icon colors
var iconColor = if (holder.container.isSelected)
mColorOnAccentColor
else when (subNode.type) {
Type.GROUP -> mTextColorPrimary
Type.ENTRY -> mTextColor
}
// Specific elements for entry
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
@@ -437,7 +424,7 @@ class NodesAdapter (private val context: Context,
if (entry.containsAttachment()) View.VISIBLE else View.GONE
// Assign colors
val backgroundColor = entry.backgroundColor
val backgroundColor = if (mShowEntryColors) entry.backgroundColor else null
if (!holder.container.isSelected) {
if (backgroundColor != null) {
holder.container.setBackgroundColor(backgroundColor)
@@ -447,7 +434,7 @@ class NodesAdapter (private val context: Context,
} else {
holder.container.setBackgroundColor(mColorAccentLight)
}
val foregroundColor = entry.foregroundColor
val foregroundColor = if (mShowEntryColors) entry.foregroundColor else null
if (!holder.container.isSelected) {
if (foregroundColor != null) {
holder.text.setTextColor(foregroundColor)
@@ -456,13 +443,7 @@ class NodesAdapter (private val context: Context,
holder.otpProgress?.setIndicatorColor(foregroundColor)
holder.attachmentIcon?.setColorFilter(foregroundColor)
holder.meta.setTextColor(foregroundColor)
holder.icon.apply {
database.iconDrawableFactory.assignDatabaseIcon(
this,
subNode.icon,
foregroundColor
)
}
iconColor = foregroundColor
} else {
holder.text.setTextColor(mTextColor)
holder.subText?.setTextColor(mTextColorSecondary)
@@ -472,12 +453,12 @@ class NodesAdapter (private val context: Context,
holder.meta.setTextColor(mTextColor)
}
} else {
holder.text.setTextColor(mTextColorInverse)
holder.subText?.setTextColor(mTextColorInverse)
holder.otpToken?.setTextColor(mTextColorInverse)
holder.otpProgress?.setIndicatorColor(mTextColorInverse)
holder.attachmentIcon?.setColorFilter(mTextColorInverse)
holder.meta.setTextColor(mTextColorInverse)
holder.text.setTextColor(mColorOnAccentColor)
holder.subText?.setTextColor(mColorOnAccentColor)
holder.otpToken?.setTextColor(mColorOnAccentColor)
holder.otpProgress?.setIndicatorColor(mColorOnAccentColor)
holder.attachmentIcon?.setColorFilter(mColorOnAccentColor)
holder.meta.setTextColor(mColorOnAccentColor)
}
database.stopManageEntry(entry)
@@ -498,6 +479,17 @@ class NodesAdapter (private val context: Context,
}
}
// Assign image
holder.imageIdentifier?.setColorFilter(iconColor)
holder.icon.apply {
database.iconDrawableFactory.assignDatabaseIcon(this, subNode.icon, iconColor)
// Relative size of the icon
layoutParams?.apply {
height = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
width = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
}
}
// Assign click
holder.container.setOnClickListener {
mNodeClickCallback?.onNodeClick(database, subNode)

View File

@@ -1,180 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.database.Cursor
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.cursor.EntryCursorKDB
import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.strikeOut
class SearchEntryCursorAdapter(private val context: Context,
private val database: Database)
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
private val cursorInflater: LayoutInflater? = context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
private var mDisplayUsername: Boolean = false
private var mOmitBackup: Boolean = true
private val iconColor: Int
init {
// Get the icon color
val taTextColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
this.iconColor = taTextColor.getColor(0, Color.WHITE)
taTextColor.recycle()
reInit(context)
}
fun reInit(context: Context) {
this.mDisplayUsername = PreferencesUtil.showUsernamesListEntries(context)
this.mOmitBackup = PreferencesUtil.omitBackup(context)
}
override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
val view = cursorInflater!!.inflate(R.layout.item_search_entry, parent, false)
val viewHolder = ViewHolder()
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon)
viewHolder.textViewTitle = view.findViewById(R.id.entry_text)
viewHolder.textViewSubTitle = view.findViewById(R.id.entry_subtext)
view.tag = viewHolder
return view
}
override fun bindView(view: View, context: Context, cursor: Cursor) {
getEntryFrom(cursor)?.let { currentEntry ->
val viewHolder = view.tag as ViewHolder
// Assign image
viewHolder.imageViewIcon?.let { iconView ->
database.iconDrawableFactory.assignDatabaseIcon(iconView, currentEntry.icon, iconColor)
}
// Assign title
viewHolder.textViewTitle?.apply {
text = currentEntry.getVisualTitle()
strikeOut(currentEntry.isCurrentlyExpires)
}
// Assign subtitle
viewHolder.textViewSubTitle?.apply {
val entryUsername = currentEntry.username
text = if (mDisplayUsername && entryUsername.isNotEmpty()) {
String.format("(%s)", entryUsername)
} else {
""
}
visibility = if (text.isEmpty()) View.GONE else View.VISIBLE
strikeOut(currentEntry.isCurrentlyExpires)
}
}
}
private fun getEntryFrom(cursor: Cursor): Entry? {
return database.createEntry()?.apply {
entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB,
{ standardIconId ->
database.getStandardIcon(standardIconId)
},
{ customIconId ->
database.getCustomIcon(customIconId)
}
)
}
entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX,
{ standardIconId ->
database.getStandardIcon(standardIconId)
},
{ customIconId ->
database.getCustomIcon(customIconId)
}
)
}
}
}
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return searchEntries(context, constraint.toString())
}
private fun searchEntries(context: Context, query: String): Cursor? {
var cursorKDB: EntryCursorKDB? = null
var cursorKDBX: EntryCursorKDBX? = null
if (database.type == DatabaseKDB.TYPE)
cursorKDB = EntryCursorKDB()
if (database.type == DatabaseKDBX.TYPE)
cursorKDBX = EntryCursorKDBX()
val searchGroup = database.createVirtualGroupFromSearch(query,
mOmitBackup,
SearchHelper.MAX_SEARCH_ENTRY)
if (searchGroup != null) {
// Search in hide entries but not meta-stream
for (entry in searchGroup.getFilteredChildEntries(Group.ChildFilter.getDefaults(context))) {
database.startManageEntry(entry)
entry.entryKDB?.let {
cursorKDB?.addEntry(it)
}
entry.entryKDBX?.let {
cursorKDBX?.addEntry(it)
}
database.stopManageEntry(entry)
}
}
return cursorKDB ?: cursorKDBX
}
fun getEntryFromPosition(position: Int): Entry? {
var pwEntry: Entry? = null
val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) {
pwEntry = getEntryFrom(cursor)
}
return pwEntry
}
private class ViewHolder {
var imageViewIcon: ImageView? = null
var textViewTitle: TextView? = null
var textViewSubTitle: TextView? = null
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2021 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.Tags
class TagsAdapter(context: Context) : RecyclerView.Adapter<TagsAdapter.TagViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var mTags: Tags = Tags()
var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagViewHolder {
val view = inflater.inflate(R.layout.item_tag, parent, false)
return TagViewHolder(view)
}
override fun onBindViewHolder(holder: TagViewHolder, position: Int) {
val field = mTags.get(position)
holder.name.text = field
holder.bind(field, onItemClickListener)
}
override fun getItemCount(): Int {
return mTags.size()
}
fun setTags(tags: Tags) {
mTags.setTags(tags)
notifyDataSetChanged()
}
fun clear() {
mTags.clear()
}
interface OnItemClickListener {
fun onItemClick(item: String)
}
inner class TagViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var name: TextView = itemView.findViewById(R.id.tag_name)
fun bind(item: String, listener: OnItemClickListener?) {
itemView.setOnClickListener { listener?.onItemClick(item) }
}
}
}

View File

@@ -0,0 +1,19 @@
package com.kunzisoft.keepass.adapters
import android.content.Context
import com.kunzisoft.keepass.database.element.Tags
import com.tokenautocomplete.FilteredArrayAdapter
class TagsProposalAdapter(context: Context, proposal: Tags?)
: FilteredArrayAdapter<String>(
context,
android.R.layout.simple_list_item_1,
(proposal ?: Tags()).toList()
) {
override fun keepObject(obj: String, mask: String?): Boolean {
if (mask == null)
return false
return obj.contains(mask, true)
}
}

View File

@@ -9,23 +9,23 @@ import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.icons.IconDrawableFactory
class TemplatesSelectorAdapter(private val context: Context,
class TemplatesSelectorAdapter(
context: Context,
private var templates: List<Template>): BaseAdapter() {
var iconDrawableFactory: IconDrawableFactory? = null
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var mIconColor = Color.BLACK
private var mTextColor = Color.BLACK
init {
val taIconColor = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
mIconColor = taIconColor.getColor(0, Color.BLACK)
taIconColor.recycle()
val taTextColor = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
mTextColor = taTextColor.getColor(0, Color.BLACK)
taTextColor.recycle()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@@ -36,6 +36,7 @@ class TemplatesSelectorAdapter(private val context: Context,
if (templateView == null) {
holder = TemplateSelectorViewHolder()
templateView = inflater.inflate(R.layout.item_template, parent, false)
holder.background = templateView?.findViewById(R.id.template_background)
holder.icon = templateView?.findViewById(R.id.template_image)
holder.name = templateView?.findViewById(R.id.template_name)
templateView?.tag = holder
@@ -43,10 +44,15 @@ class TemplatesSelectorAdapter(private val context: Context,
holder = templateView.tag as TemplateSelectorViewHolder
}
holder.background?.setBackgroundColor(template.backgroundColor ?: Color.TRANSPARENT)
val textColor = template.foregroundColor ?: mTextColor
holder.icon?.let { icon ->
iconDrawableFactory?.assignDatabaseIcon(icon, template.icon, mIconColor)
iconDrawableFactory?.assignDatabaseIcon(icon, template.icon, textColor)
}
holder.name?.apply {
setTextColor(textColor)
text = TemplateField.getLocalizedName(context, template.title)
}
holder.name?.text = TemplateField.getLocalizedName(context, template.title)
return templateView!!
}
@@ -64,6 +70,7 @@ class TemplatesSelectorAdapter(private val context: Context,
}
inner class TemplateSelectorViewHolder {
var background: View? = null
var icon: ImageView? = null
var name: TextView? = null
}

View File

@@ -22,7 +22,9 @@ package com.kunzisoft.keepass.app.database
import android.content.*
import android.net.Uri
import android.os.IBinder
import android.util.Base64
import android.util.Log
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.SingletonHolderParameter
@@ -125,15 +127,41 @@ class CipherDatabaseAction(context: Context) {
}
fun getCipherDatabase(databaseUri: Uri,
cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) {
cipherDatabaseResultListener: (CipherEncryptDatabase?) -> Unit) {
if (useTempDao) {
serviceActionTask {
cipherDatabaseResultListener.invoke(mBinder?.getCipherDatabase(databaseUri))
var cipherDatabase: CipherEncryptDatabase? = null
mBinder?.getCipherDatabase(databaseUri)?.let { cipherDatabaseEntity ->
cipherDatabase = CipherEncryptDatabase().apply {
this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri)
this.encryptedValue = Base64.decode(
cipherDatabaseEntity.encryptedValue,
Base64.NO_WRAP
)
this.specParameters = Base64.decode(
cipherDatabaseEntity.specParameters,
Base64.NO_WRAP
)
}
}
cipherDatabaseResultListener.invoke(cipherDatabase)
}
} else {
IOActionTask(
{
cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())?.let { cipherDatabaseEntity ->
CipherEncryptDatabase().apply {
this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri)
this.encryptedValue = Base64.decode(
cipherDatabaseEntity.encryptedValue,
Base64.NO_WRAP
)
this.specParameters = Base64.decode(
cipherDatabaseEntity.specParameters,
Base64.NO_WRAP
)
}
}
},
{
cipherDatabaseResultListener.invoke(it)
@@ -149,8 +177,16 @@ class CipherDatabaseAction(context: Context) {
}
}
fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity,
fun addOrUpdateCipherDatabase(cipherEncryptDatabase: CipherEncryptDatabase,
cipherDatabaseResultListener: (() -> Unit)? = null) {
cipherEncryptDatabase.databaseUri?.let { databaseUri ->
val cipherDatabaseEntity = CipherDatabaseEntity(
databaseUri.toString(),
Base64.encodeToString(cipherEncryptDatabase.encryptedValue, Base64.NO_WRAP),
Base64.encodeToString(cipherEncryptDatabase.specParameters, Base64.NO_WRAP),
)
if (useTempDao) {
// The only case to create service (not needed to get an info)
serviceActionTask(true) {
@@ -160,7 +196,8 @@ class CipherDatabaseAction(context: Context) {
} else {
IOActionTask(
{
val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
val cipherDatabaseRetrieve =
cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
// Update values if element not yet in the database
if (cipherDatabaseRetrieve == null) {
cipherDatabaseDao.add(cipherDatabaseEntity)
@@ -174,6 +211,7 @@ class CipherDatabaseAction(context: Context) {
).execute()
}
}
}
fun deleteByDatabaseUri(databaseUri: Uri,
cipherDatabaseResultListener: (() -> Unit)? = null) {

View File

@@ -377,10 +377,12 @@ class StructureParser(private val structure: AssistStructure) {
when {
inputIsVariationType(inputType,
InputType.TYPE_NUMBER_VARIATION_NORMAL) -> {
if (usernameIdCandidate == null) {
usernameIdCandidate = autofillId
usernameValueCandidate = node.autofillValue
Log.d(TAG, "Autofill username candidate android number type: ${showHexInputType(inputType)}")
}
}
inputIsVariationType(inputType,
InputType.TYPE_NUMBER_VARIATION_PASSWORD) -> {
result?.passwordId = autofillId

View File

@@ -40,8 +40,13 @@ import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.exception.IODatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.model.CipherDecryptDatabase
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.CredentialStorage
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -60,6 +65,9 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
var databaseFileUri: Uri? = null
private set
// TODO Retrieve credential storage from app database
var credentialDatabaseStorage: CredentialStorage = CredentialStorage.DEFAULT
/**
* Manage setting to auto open biometric prompt
*/
@@ -477,6 +485,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
} ?: checkUnlockAvailability()
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
lifecycleScope.launch(Dispatchers.Main) {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
@@ -528,16 +537,29 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {
databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec)
mBuilderListener?.onCredentialEncrypted(
CipherEncryptDatabase().apply {
this.databaseUri = databaseUri
this.credentialStorage = credentialDatabaseStorage
this.encryptedValue = encryptedValue
this.specParameters = ivSpec
}
)
}
}
override fun handleDecryptedResult(decryptedValue: String) {
override fun handleDecryptedResult(decryptedValue: ByteArray) {
// Load database directly with password retrieve
databaseFileUri?.let {
mBuilderListener?.onCredentialDecrypted(it, decryptedValue)
databaseFileUri?.let { databaseUri ->
mBuilderListener?.onCredentialDecrypted(
CipherDecryptDatabase().apply {
this.databaseUri = databaseUri
this.credentialStorage = credentialDatabaseStorage
this.decryptedValue = decryptedValue
}
)
}
}
@@ -551,6 +573,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onGenericException(e: Exception) {
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
setAdvancedUnlockedMessageView(errorMessage)
@@ -558,10 +581,13 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private fun showViews(show: Boolean) {
lifecycleScope.launch(Dispatchers.Main) {
mAdvancedUnlockInfoView?.visibility = if (show)
View.VISIBLE
if (show) {
if (mAdvancedUnlockInfoView?.visibility != View.VISIBLE)
mAdvancedUnlockInfoView?.showByFading()
}
else {
View.GONE
if (mAdvancedUnlockInfoView?.visibility == View.VISIBLE)
mAdvancedUnlockInfoView?.hideByFading()
}
}
}
@@ -580,32 +606,13 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
lifecycleScope.launch(Dispatchers.Main) {
mAdvancedUnlockInfoView?.message = text
}
}
fun performEducation(passwordActivityEducation: PasswordActivityEducation,
readOnlyEducationPerformed: Boolean,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
onEducationViewClick,
onOuterViewClick)
}
} catch (ignored: Exception) {}
}
enum class Mode {
BIOMETRIC_UNAVAILABLE,
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
@@ -617,10 +624,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
interface BuilderListener {
fun retrieveCredentialForEncryption(): String
fun retrieveCredentialForEncryption(): ByteArray
fun conditionToStoreCredential(): Boolean
fun onCredentialEncrypted(databaseUri: Uri, encryptedCredential: String, ivSpec: String)
fun onCredentialDecrypted(databaseUri: Uri, decryptedCredential: String)
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase)
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase)
}
override fun onPause() {

View File

@@ -27,7 +27,6 @@ import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
@@ -125,7 +124,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
private fun getSecretKey(): SecretKey? {
@Synchronized private fun getSecretKey(): SecretKey? {
if (!isKeyManagerInitialized) {
return null
}
@@ -142,8 +141,8 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
KeyGenParameterSpec.Builder(
ADVANCED_UNLOCK_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setBlockModes(ADVANCED_UNLOCK_BLOCKS_MODES)
.setEncryptionPaddings(ADVANCED_UNLOCK_ENCRYPTION_PADDING)
.apply {
// Require the user to authenticate with a fingerprint to authorize every use
// of the key, don't use it for device credential because it's the user authentication
@@ -174,11 +173,11 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
return null
}
fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,) {
@Synchronized fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,) {
initEncryptData(actionIfCypherInit, true)
}
private fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
@Synchronized private fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean) {
if (!isKeyManagerInitialized) {
return
@@ -214,18 +213,15 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun encryptData(value: String) {
@Synchronized fun encryptData(value: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
try {
val encrypted = cipher?.doFinal(value.toByteArray())
val encryptedBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)
val encrypted = cipher?.doFinal(value) ?: byteArrayOf()
// passes updated iv spec on to callback so this can be stored for decryption
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
advancedUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
advancedUnlockCallback?.handleEncryptedResult(encrypted, spec.iv)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to encrypt data", e)
@@ -233,12 +229,12 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun initDecryptData(ivSpecValue: String,
@Synchronized fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
initDecryptData(ivSpecValue, actionIfCypherInit, true)
}
private fun initDecryptData(ivSpecValue: String,
@Synchronized private fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean = true) {
if (!isKeyManagerInitialized) {
@@ -246,9 +242,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
try {
// important to restore spec here that was used for decryption
val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP)
val spec = IvParameterSpec(iv)
val spec = IvParameterSpec(ivSpecValue)
getSecretKey()?.let { secretKey ->
cipher?.let { cipher ->
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
@@ -284,15 +278,14 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun decryptData(encryptedValue: String) {
@Synchronized fun decryptData(encryptedValue: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
try {
// actual decryption here
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
cipher?.doFinal(encrypted)?.let { decrypted ->
advancedUnlockCallback?.handleDecryptedResult(String(decrypted))
cipher?.doFinal(encryptedValue)?.let { decrypted ->
advancedUnlockCallback?.handleDecryptedResult(decrypted)
}
} catch (badPaddingException: BadPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException)
@@ -303,7 +296,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun deleteKeystoreKey() {
@Synchronized fun deleteKeystoreKey() {
try {
keyStore?.load(null)
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
@@ -313,7 +306,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt,
@Synchronized fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt,
deviceCredentialResultLauncher: ActivityResultLauncher<Intent>
) {
// Init advanced unlock prompt
@@ -353,7 +346,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun closeBiometricPrompt() {
@Synchronized fun closeBiometricPrompt() {
biometricPrompt?.cancelAuthentication()
}
@@ -367,8 +360,8 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
fun onAuthenticationSucceeded()
fun onAuthenticationFailed()
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
fun handleEncryptedResult(encryptedValue: String, ivSpec: String)
fun handleDecryptedResult(decryptedValue: String)
fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray)
fun handleDecryptedResult(decryptedValue: ByteArray)
}
companion object {
@@ -469,9 +462,9 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {}
override fun handleDecryptedResult(decryptedValue: String) {}
override fun handleDecryptedResult(decryptedValue: ByteArray) {}
override fun onUnrecoverableKeyException(e: Exception) {
advancedCallback.onUnrecoverableKeyException(e)

View File

@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
open class AssignPasswordInDatabaseRunnable (
open class AssignMainCredentialInDatabaseRunnable (
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
@@ -43,7 +43,7 @@ open class AssignPasswordInDatabaseRunnable (
System.arraycopy(database.masterKey, 0, mBackupKey!!, 0, mBackupKey!!.size)
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mMainCredential.keyFileUri)
database.retrieveMasterKey(mMainCredential.masterPassword, uriInputStream)
database.assignMasterKey(mMainCredential.masterPassword, uriInputStream)
} catch (e: Exception) {
erase(mBackupKey)
setError(e)

View File

@@ -35,7 +35,7 @@ class CreateDatabaseRunnable(context: Context,
private val templateGroupName: String?,
mainCredential: MainCredential,
private val createDatabaseResult: ((Result) -> Unit)?)
: AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential) {
: AssignMainCredentialInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential) {
override fun onStartRun() {
try {

View File

@@ -27,12 +27,12 @@ import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -53,6 +54,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MERGE_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
@@ -82,7 +84,6 @@ import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import kotlinx.coroutines.launch
import java.util.*
import kotlin.collections.ArrayList
/**
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
@@ -342,18 +343,27 @@ class DatabaseTaskProvider {
fun startDatabaseLoad(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
cipherEntity: CipherDatabaseEntity?,
cipherEncryptDatabase: CipherEncryptDatabase?,
fixDuplicateUuid: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
putBoolean(DatabaseTaskNotificationService.READ_ONLY_KEY, readOnly)
putParcelable(DatabaseTaskNotificationService.CIPHER_ENTITY_KEY, cipherEntity)
putParcelable(DatabaseTaskNotificationService.CIPHER_DATABASE_KEY, cipherEncryptDatabase)
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
}
, ACTION_DATABASE_LOAD_TASK)
}
fun startDatabaseMerge(fromDatabaseUri: Uri? = null,
mainCredential: MainCredential? = null) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, fromDatabaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
}
, ACTION_DATABASE_MERGE_TASK)
}
fun startDatabaseReload(fixDuplicateUuid: Boolean) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
@@ -361,6 +371,19 @@ class DatabaseTaskProvider {
, ACTION_DATABASE_RELOAD_TASK)
}
fun askToStartDatabaseReload(conditionToAsk: Boolean, approved: () -> Unit) {
if (conditionToAsk) {
AlertDialog.Builder(context)
.setMessage(R.string.warning_database_info_reloaded)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->
approved.invoke()
}.create().show()
} else {
approved.invoke()
}
}
fun startDatabaseAssignPassword(databaseUri: Uri,
mainCredential: MainCredential) {
@@ -671,9 +694,10 @@ class DatabaseTaskProvider {
/**
* Save Database without parameter
*/
fun startDatabaseSave(save: Boolean) {
fun startDatabaseSave(save: Boolean, saveToUri: Uri? = null) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, saveToUri)
}
, ACTION_DATABASE_SAVE)
}

View File

@@ -22,12 +22,11 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -39,7 +38,7 @@ class LoadDatabaseRunnable(private val context: Context,
private val mUri: Uri,
private val mMainCredential: MainCredential,
private val mReadonly: Boolean,
private val mCipherEntity: CipherDatabaseEntity?,
private val mCipherEncryptDatabase: CipherEncryptDatabase?,
private val mFixDuplicateUUID: Boolean,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
@@ -60,7 +59,6 @@ class LoadDatabaseRunnable(private val context: Context,
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
LoadedKey.generateNewCipherKey(),
mFixDuplicateUUID,
progressTaskUpdater)
}
@@ -77,9 +75,9 @@ class LoadDatabaseRunnable(private val context: Context,
}
// Register the biometric
mCipherEntity?.let { cipherDatabaseEntity ->
mCipherEncryptDatabase?.let { cipherDatabase ->
CipherDatabaseAction.getInstance(context)
.addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
.addOrUpdateCipherDatabase(cipherDatabase) // return value not called
}
// Register the current time to init the lock timer

View File

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

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -35,23 +34,18 @@ class ReloadDatabaseRunnable(private val context: Context,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
: ActionRunnable() {
private var tempCipherKey: LoadedKey? = null
override fun onStartRun() {
tempCipherKey = mDatabase.binaryCache.loadedCipherKey
// Clear before we load
mDatabase.clear(UriUtil.getBinaryDir(context))
mDatabase.clearIndexesAndBinaries(UriUtil.getBinaryDir(context))
mDatabase.wasReloaded = true
}
override fun onActionRun() {
try {
mDatabase.reloadData(context.contentResolver,
UriUtil.getBinaryDir(context),
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
tempCipherKey ?: LoadedKey.generateNewCipherKey(),
progressTaskUpdater)
} catch (e: LoadDatabaseException) {
setError(e)
@@ -61,7 +55,6 @@ class ReloadDatabaseRunnable(private val context: Context,
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
} else {
tempCipherKey = null
mDatabase.clearAndClose(context)
}
}

View File

@@ -20,13 +20,15 @@
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.tasks.ActionRunnable
open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
private var saveDatabase: Boolean)
private var saveDatabase: Boolean,
private var databaseCopyUri: Uri? = null)
: ActionRunnable() {
var mAfterSaveDatabase: ((Result) -> Unit)? = null
@@ -34,9 +36,10 @@ open class SaveDatabaseRunnable(protected var context: Context,
override fun onStartRun() {}
override fun onActionRun() {
database.checkVersion()
if (saveDatabase && result.isSuccess) {
try {
database.saveData(context.contentResolver)
database.saveData(databaseCopyUri, context.contentResolver)
} catch (e: DatabaseException) {
setError(e)
}

View File

@@ -40,7 +40,7 @@ class DeleteNodesRunnable(context: Context,
foreachNode@ for(nodeToDelete in mNodesToDelete) {
mOldParent = nodeToDelete.parent
mOldParent?.touch(modified = false, touchParents = true)
nodeToDelete.touch(modified = true, touchParents = true)
when (nodeToDelete.type) {
Type.GROUP -> {
@@ -50,9 +50,9 @@ class DeleteNodesRunnable(context: Context,
// Remove Node from parent
mCanRecycle = database.canRecycle(groupToDelete)
if (mCanRecycle) {
groupToDelete.touch(modified = false, touchParents = true)
database.recycle(groupToDelete, context.resources)
groupToDelete.setPreviousParentGroup(mOldParent)
groupToDelete.touch(modified = true, touchParents = true)
} else {
database.deleteGroup(groupToDelete)
}
@@ -64,9 +64,9 @@ class DeleteNodesRunnable(context: Context,
// Remove Node from parent
mCanRecycle = database.canRecycle(entryToDelete)
if (mCanRecycle) {
entryToDelete.touch(modified = false, touchParents = true)
database.recycle(entryToDelete, context.resources)
entryToDelete.setPreviousParentGroup(mOldParent)
entryToDelete.touch(modified = true, touchParents = true)
} else {
database.deleteEntry(entryToDelete)
}

View File

@@ -43,6 +43,7 @@ class MoveNodesRunnable constructor(
foreachNode@ for(nodeToMove in mNodesToMove) {
// Move node in new parent
mOldParent = nodeToMove.parent
nodeToMove.touch(modified = true, touchParents = true)
when (nodeToMove.type) {
Type.GROUP -> {
@@ -52,9 +53,9 @@ class MoveNodesRunnable constructor(
// and if not in the current group
&& groupToMove != mNewParent
&& !mNewParent.isContainedIn(groupToMove)) {
groupToMove.touch(modified = true, touchParents = true)
database.moveGroupTo(groupToMove, mNewParent)
groupToMove.setPreviousParentGroup(mOldParent)
groupToMove.touch(modified = true, touchParents = true)
} else {
// Only finish thread
setError(MoveGroupDatabaseException())
@@ -67,9 +68,9 @@ class MoveNodesRunnable constructor(
if (mOldParent != mNewParent
// and root can contains entry
&& (mNewParent != database.rootGroup || database.rootCanContainsEntry())) {
entryToMove.touch(modified = true, touchParents = true)
database.moveEntryTo(entryToMove, mNewParent)
entryToMove.setPreviousParentGroup(mOldParent)
entryToMove.touch(modified = true, touchParents = true)
} else {
// Only finish thread
setError(MoveEntryDatabaseException())

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId
import java.util.*
abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>> : MatrixCursor(arrayOf(
_ID,
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
COLUMN_INDEX_TITLE,
COLUMN_INDEX_ICON_STANDARD,
COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS,
COLUMN_INDEX_USERNAME,
COLUMN_INDEX_PASSWORD,
COLUMN_INDEX_URL,
COLUMN_INDEX_NOTES,
COLUMN_INDEX_EXPIRY_TIME,
COLUMN_INDEX_EXPIRES
)) {
protected var entryId: Long = 0
abstract fun addEntry(entry: PwEntryV)
abstract fun getPwNodeId(): NodeId<EntryId>
open fun populateEntry(pwEntry: PwEntryV,
retrieveStandardIcon: (Int) -> IconImageStandard,
retrieveCustomIcon: (UUID) -> IconImageCustom) {
pwEntry.nodeId = getPwNodeId()
pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE))
val iconStandard = retrieveStandardIcon.invoke(getInt(getColumnIndex(COLUMN_INDEX_ICON_STANDARD)))
val iconCustom = retrieveCustomIcon.invoke(UUID(getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
pwEntry.icon = IconImage(iconStandard, iconCustom)
pwEntry.username = getString(getColumnIndex(COLUMN_INDEX_USERNAME))
pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD))
pwEntry.url = getString(getColumnIndex(COLUMN_INDEX_URL))
pwEntry.notes = getString(getColumnIndex(COLUMN_INDEX_NOTES))
pwEntry.expiryTime = DateInstant(getString(getColumnIndex(COLUMN_INDEX_EXPIRY_TIME)))
pwEntry.expires = getString(getColumnIndex(COLUMN_INDEX_EXPIRES))
.lowercase(Locale.ENGLISH) != "false"
}
companion object {
const val _ID = BaseColumns._ID
const val COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUID_most_significant_bits"
const val COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUID_least_significant_bits"
const val COLUMN_INDEX_TITLE = "title"
const val COLUMN_INDEX_ICON_STANDARD = "icon_standard"
const val COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS = "icon_custom_UUID_most_significant_bits"
const val COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS = "icon_custom_UUID_least_significant_bits"
const val COLUMN_INDEX_USERNAME = "username"
const val COLUMN_INDEX_PASSWORD = "password"
const val COLUMN_INDEX_URL = "URL"
const val COLUMN_INDEX_NOTES = "notes"
const val COLUMN_INDEX_EXPIRY_TIME = "expiry_time"
const val COLUMN_INDEX_EXPIRES = "expires"
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryKDB
class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
override fun addEntry(entry: EntryKDB) {
addRow(arrayOf(
entryId,
entry.id.mostSignificantBits,
entry.id.leastSignificantBits,
entry.title,
entry.icon.standard.id,
entry.icon.custom.uuid.mostSignificantBits,
entry.icon.custom.uuid.leastSignificantBits,
entry.username,
entry.password,
entry.url,
entry.notes,
entry.expiryTime,
entry.expires
))
entryId++
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import java.util.*
class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
private val extraFieldCursor: ExtraFieldCursor = ExtraFieldCursor()
override fun addEntry(entry: EntryKDBX) {
addRow(arrayOf(
entryId,
entry.id.mostSignificantBits,
entry.id.leastSignificantBits,
entry.title,
entry.icon.standard.id,
entry.icon.custom.uuid.mostSignificantBits,
entry.icon.custom.uuid.leastSignificantBits,
entry.username,
entry.password,
entry.url,
entry.notes,
entry.expiryTime,
entry.expires
))
entry.doForEachDecodedCustomField { field ->
extraFieldCursor.addExtraField(entryId, field)
}
entryId++
}
override fun populateEntry(pwEntry: EntryKDBX,
retrieveStandardIcon: (Int) -> IconImageStandard,
retrieveCustomIcon: (UUID) -> IconImageCustom) {
super.populateEntry(pwEntry, retrieveStandardIcon, retrieveCustomIcon)
// Retrieve extra fields
if (extraFieldCursor.moveToFirst()) {
while (!extraFieldCursor.isAfterLast) {
// Add a new extra field only if entryId is the one we want
if (extraFieldCursor.getLong(extraFieldCursor
.getColumnIndex(ExtraFieldCursor.FOREIGN_KEY_ENTRY_ID))
== getLong(getColumnIndex(_ID))) {
extraFieldCursor.populateExtraFieldInEntry(pwEntry)
}
extraFieldCursor.moveToNext()
}
}
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import java.util.*
abstract class EntryCursorUUID<EntryV: EntryVersioned<*, UUID, *, *>>: EntryCursor<UUID, EntryV>() {
override fun getPwNodeId(): NodeId<UUID> {
return NodeIdUUID(
UUID(getLong(getColumnIndex(COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))))
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.security.ProtectedString
class ExtraFieldCursor : MatrixCursor(arrayOf(
_ID,
FOREIGN_KEY_ENTRY_ID,
COLUMN_LABEL,
COLUMN_PROTECTION,
COLUMN_VALUE
)) {
private var fieldId: Long = 0
@Synchronized
fun addExtraField(entryId: Long, field: Field) {
addRow(arrayOf(fieldId,
entryId,
field.name,
if (field.protectedValue.isProtected) 1 else 0,
field.protectedValue.toString()))
fieldId++
}
fun populateExtraFieldInEntry(pwEntry: EntryKDBX) {
pwEntry.putField(getString(getColumnIndex(COLUMN_LABEL)),
ProtectedString(getInt(getColumnIndex(COLUMN_PROTECTION)) > 0,
getString(getColumnIndex(COLUMN_VALUE))))
}
companion object {
const val _ID = BaseColumns._ID
const val FOREIGN_KEY_ENTRY_ID = "entry_id"
const val COLUMN_LABEL = "label"
const val COLUMN_PROTECTION = "protection"
const val COLUMN_VALUE = "value"
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.os.Parcel
@@ -17,7 +36,10 @@ class CustomData : Parcelable {
}
constructor(parcel: Parcel) {
ParcelableUtil.readStringParcelableMap(parcel, CustomDataItem::class.java)
mCustomDataItems.clear()
mCustomDataItems.putAll(ParcelableUtil
.readStringParcelableMap(parcel, CustomDataItem::class.java)
)
}
fun get(key: String): CustomDataItem? {
@@ -46,6 +68,10 @@ class CustomData : Parcelable {
}
}
override fun toString(): String {
return mCustomDataItems.toString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
ParcelableUtil.writeStringParcelableMap(parcel, flags, mCustomDataItems)
}

View File

@@ -21,6 +21,10 @@ class CustomDataItem : Parcelable {
this.lastModificationTime = lastModificationTime
}
override fun toString(): String {
return value
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(key)
parcel.writeString(value)

View File

@@ -32,7 +32,6 @@ import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
@@ -52,6 +51,7 @@ import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
import com.kunzisoft.keepass.database.merge.DatabaseKDBXMerger
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.icons.IconDrawableFactory
@@ -62,7 +62,6 @@ import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import java.io.*
import java.util.*
import kotlin.collections.ArrayList
class Database {
@@ -94,6 +93,8 @@ class Database {
*/
var wasReloaded = false
var dataModifiedSinceLastLoading = false
var loadTimestamp: Long? = null
private set
@@ -112,7 +113,7 @@ class Database {
private val iconsManager: IconsManager
get() {
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager(binaryCache)
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager()
}
fun doForEachStandardIcons(action: (IconImageStandard) -> Unit) {
@@ -130,7 +131,7 @@ class Database {
return iconsManager.doForEachCustomIcon(action)
}
fun getCustomIcon(iconId: UUID): IconImageCustom {
fun getCustomIcon(iconId: UUID): IconImageCustom? {
return iconsManager.getIcon(iconId)
}
@@ -144,11 +145,12 @@ class Database {
fun removeCustomIcon(customIcon: IconImageCustom) {
iconDrawableFactory.clearFromCache(customIcon)
iconsManager.removeCustomIcon(binaryCache, customIcon.uuid)
iconsManager.removeCustomIcon(customIcon.uuid, binaryCache)
mDatabaseKDBX?.addDeletedObject(customIcon.uuid)
}
fun updateCustomIcon(customIcon: IconImageCustom) {
iconsManager.getIcon(customIcon.uuid).updateWith(customIcon)
iconsManager.getIcon(customIcon.uuid)?.updateWith(customIcon)
}
fun getTemplates(templateCreation: Boolean): List<Template> {
@@ -212,6 +214,7 @@ class Database {
set(name) {
mDatabaseKDBX?.name = name
mDatabaseKDBX?.nameChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
val allowDescription: Boolean
@@ -224,6 +227,7 @@ class Database {
set(description) {
mDatabaseKDBX?.description = description
mDatabaseKDBX?.descriptionChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
var defaultUsername: String
@@ -234,6 +238,7 @@ class Database {
mDatabaseKDB?.defaultUserName = username
mDatabaseKDBX?.defaultUserName = username
mDatabaseKDBX?.defaultUserNameChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
var customColor: Int?
@@ -253,6 +258,8 @@ class Database {
} else {
ChromaUtil.getFormattedColorString(value, false)
}
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
val allowOTP: Boolean
@@ -261,6 +268,15 @@ class Database {
val version: String
get() = mDatabaseKDB?.version ?: mDatabaseKDBX?.version ?: "-"
fun checkVersion() {
mDatabaseKDBX?.getMinKdbxVersion()?.let {
mDatabaseKDBX?.kdbxVersion = it
}
}
val defaultFileExtension: String
get() = mDatabaseKDB?.defaultFileExtension ?: mDatabaseKDBX?.defaultFileExtension ?: ".bin"
val type: Class<*>?
get() = mDatabaseKDB?.javaClass ?: mDatabaseKDBX?.javaClass
@@ -276,6 +292,8 @@ class Database {
value?.let {
mDatabaseKDBX?.compressionAlgorithm = it
}
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
fun compressionForNewEntry(): Boolean {
@@ -292,6 +310,7 @@ class Database {
fun updateDataBinaryCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) {
mDatabaseKDBX?.changeBinaryCompression(oldCompression, newCompression)
dataModifiedSinceLastLoading = true
}
val allowNoMasterKey: Boolean
@@ -311,8 +330,6 @@ class Database {
set(algorithm) {
algorithm?.let {
mDatabaseKDBX?.encryptionAlgorithm = algorithm
mDatabaseKDBX?.setDataEngine(algorithm.cipherEngine)
mDatabaseKDBX?.cipherUuid = algorithm.uuid
}
}
@@ -325,13 +342,10 @@ class Database {
var kdfEngine: KdfEngine?
get() = mDatabaseKDB?.kdfEngine ?: mDatabaseKDBX?.kdfEngine
set(kdfEngine) {
kdfEngine?.let {
if (mDatabaseKDBX?.kdfParameters?.uuid != kdfEngine.defaultParameters.uuid)
mDatabaseKDBX?.kdfParameters = kdfEngine.defaultParameters
numberKeyEncryptionRounds = kdfEngine.defaultKeyRounds
memoryUsage = kdfEngine.defaultMemoryUsage
parallelism = kdfEngine.defaultParallelism
}
mDatabaseKDB?.kdfEngine = kdfEngine
mDatabaseKDBX?.kdfEngine = kdfEngine
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
fun getKeyDerivationName(): String {
@@ -343,6 +357,8 @@ class Database {
set(numberRounds) {
mDatabaseKDB?.numberKeyEncryptionRounds = numberRounds
mDatabaseKDBX?.numberKeyEncryptionRounds = numberRounds
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
var memoryUsage: Long
@@ -351,12 +367,16 @@ class Database {
}
set(memory) {
mDatabaseKDBX?.memoryUsage = memory
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
var parallelism: Long
get() = mDatabaseKDBX?.parallelism ?: KdfEngine.UNKNOWN_VALUE
set(parallelism) {
mDatabaseKDBX?.parallelism = parallelism
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
var masterKey: ByteArray
@@ -364,6 +384,8 @@ class Database {
set(masterKey) {
mDatabaseKDB?.masterKey = masterKey
mDatabaseKDBX?.masterKey = masterKey
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
var rootGroup: Group?
@@ -414,6 +436,8 @@ class Database {
}
set(value) {
mDatabaseKDBX?.historyMaxItems = value
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
var historyMaxSize: Long
@@ -422,6 +446,8 @@ class Database {
}
set(value) {
mDatabaseKDBX?.historyMaxSize = value
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
/**
@@ -442,15 +468,17 @@ class Database {
} else {
mDatabaseKDBX?.removeRecycleBin()
}
mDatabaseKDBX?.recycleBinChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
val recycleBin: Group?
get() {
mDatabaseKDB?.backupGroup?.let {
return Group(it)
return getGroupById(it.nodeId) ?: Group(it)
}
mDatabaseKDBX?.recycleBin?.let {
return Group(it)
return getGroupById(it.nodeId) ?: Group(it)
}
return null
}
@@ -460,15 +488,17 @@ class Database {
if (group != null) {
mDatabaseKDBX?.recycleBinUUID = group.nodeIdKDBX.id
} else {
mDatabaseKDBX?.removeTemplatesGroup()
mDatabaseKDBX?.removeRecycleBin()
}
mDatabaseKDBX?.recycleBinChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
/**
* Determine if a configurable templates group is available or not for this version of database
* @return true if a configurable templates group available
*/
val allowConfigurableTemplatesGroup: Boolean
val allowTemplatesGroup: Boolean
get() = mDatabaseKDBX != null
// Maybe another templates method with KDBX5
@@ -477,6 +507,8 @@ class Database {
fun enableTemplates(enable: Boolean, templatesGroupName: String) {
mDatabaseKDBX?.enableTemplatesGroup(enable, templatesGroupName)
mDatabaseKDBX?.entryTemplatesGroupChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
val templatesGroup: Group?
@@ -492,8 +524,10 @@ class Database {
if (group != null) {
mDatabaseKDBX?.entryTemplatesGroup = group.nodeIdKDBX.id
} else {
mDatabaseKDBX?.entryTemplatesGroup
mDatabaseKDBX?.removeTemplatesGroup()
}
mDatabaseKDBX?.entryTemplatesGroupChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
val groupNamesNotAllowed: List<String>
@@ -520,6 +554,7 @@ class Database {
this.fileUri = databaseUri
// Set Database state
this.loaded = true
this.dataModifiedSinceLastLoading = false
}
@Throws(LoadDatabaseException::class)
@@ -576,7 +611,6 @@ class Database {
contentResolver: ContentResolver,
cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
tempCipherKey: LoadedKey,
fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
@@ -597,99 +631,231 @@ class Database {
// Read database stream for the first time
readDatabaseStream(contentResolver, uri,
{ databaseInputStream ->
DatabaseInputKDB(cacheDirectory, isRAMSufficient)
val databaseKDB = DatabaseKDB().apply {
binaryCache.cacheDirectory = cacheDirectory
changeDuplicateId = fixDuplicateUUID
}
DatabaseInputKDB(databaseKDB)
.openDatabase(databaseInputStream,
progressTaskUpdater
) {
databaseKDB.retrieveMasterKey(
mainCredential.masterPassword,
keyFileInputStream,
tempCipherKey,
progressTaskUpdater,
fixDuplicateUUID)
keyFileInputStream
)
}
databaseKDB
},
{ databaseInputStream ->
DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream,
val databaseKDBX = DatabaseKDBX().apply {
binaryCache.cacheDirectory = cacheDirectory
changeDuplicateId = fixDuplicateUUID
}
DatabaseInputKDBX(databaseKDBX).apply {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
openDatabase(databaseInputStream,
progressTaskUpdater) {
databaseKDBX.retrieveMasterKey(
mainCredential.masterPassword,
keyFileInputStream,
tempCipherKey,
progressTaskUpdater,
fixDuplicateUUID)
)
}
}
databaseKDBX
}
)
} catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to load keyfile", e)
throw FileNotFoundDatabaseException()
throw FileNotFoundDatabaseException("Unable to load the keyfile")
} catch (e: LoadDatabaseException) {
throw e
} catch (e: Exception) {
throw LoadDatabaseException(e)
} finally {
keyFileInputStream?.close()
dataModifiedSinceLastLoading = false
}
}
fun isMergeDataAllowed(): Boolean {
return mDatabaseKDBX != null
}
@Throws(LoadDatabaseException::class)
fun mergeData(databaseToMergeUri: Uri?,
databaseToMergeMainCredential: MainCredential?,
contentResolver: ContentResolver,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
mDatabaseKDB?.let {
throw IODatabaseException("Unable to merge from a database V1")
}
// New database instance to get new changes
val databaseToMerge = Database()
databaseToMerge.fileUri = databaseToMergeUri ?: this.fileUri
// Pass KeyFile Uri as InputStreams
var keyFileInputStream: InputStream? = null
try {
val databaseUri = databaseToMerge.fileUri
if (databaseUri != null) {
if (databaseToMergeMainCredential != null) {
// Get keyFile inputStream
databaseToMergeMainCredential.keyFileUri?.let { keyFile ->
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
}
}
databaseToMerge.readDatabaseStream(contentResolver, databaseUri,
{ databaseInputStream ->
val databaseToMergeKDB = DatabaseKDB()
DatabaseInputKDB(databaseToMergeKDB)
.openDatabase(databaseInputStream, progressTaskUpdater) {
if (databaseToMergeMainCredential != null) {
databaseToMergeKDB.retrieveMasterKey(
databaseToMergeMainCredential.masterPassword,
keyFileInputStream,
)
} else {
databaseToMergeKDB.masterKey = masterKey
}
}
databaseToMergeKDB
},
{ databaseInputStream ->
val databaseToMergeKDBX = DatabaseKDBX()
DatabaseInputKDBX(databaseToMergeKDBX).apply {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
openDatabase(databaseInputStream, progressTaskUpdater) {
if (databaseToMergeMainCredential != null) {
databaseToMergeKDBX.retrieveMasterKey(
databaseToMergeMainCredential.masterPassword,
keyFileInputStream,
)
} else {
databaseToMergeKDBX.masterKey = masterKey
}
}
}
databaseToMergeKDBX
}
)
mDatabaseKDBX?.let { currentDatabaseKDBX ->
val databaseMerger = DatabaseKDBXMerger(currentDatabaseKDBX).apply {
this.isRAMSufficient = isRAMSufficient
}
databaseToMerge.mDatabaseKDB?.let { databaseKDBToMerge ->
databaseMerger.merge(databaseKDBToMerge)
if (databaseToMergeUri != null) {
this.dataModifiedSinceLastLoading = true
}
}
databaseToMerge.mDatabaseKDBX?.let { databaseKDBXToMerge ->
databaseMerger.merge(databaseKDBXToMerge)
if (databaseToMergeUri != null) {
this.dataModifiedSinceLastLoading = true
}
}
}
} else {
throw IODatabaseException("Database URI is null, database cannot be merged")
}
} catch (e: FileNotFoundException) {
throw FileNotFoundDatabaseException("Unable to load the keyfile")
} catch (e: LoadDatabaseException) {
throw e
} catch (e: Exception) {
throw LoadDatabaseException(e)
} finally {
keyFileInputStream?.close()
databaseToMerge.clearAndClose()
}
}
@Throws(LoadDatabaseException::class)
fun reloadData(contentResolver: ContentResolver,
cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
tempCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?) {
// Retrieve the stream from the old database URI
try {
fileUri?.let { oldDatabaseUri ->
val oldDatabaseUri = fileUri
if (oldDatabaseUri != null) {
readDatabaseStream(contentResolver, oldDatabaseUri,
{ databaseInputStream ->
DatabaseInputKDB(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream,
masterKey,
tempCipherKey,
progressTaskUpdater)
val databaseKDB = DatabaseKDB()
mDatabaseKDB?.let {
databaseKDB.binaryCache = it.binaryCache
}
DatabaseInputKDB(databaseKDB)
.openDatabase(databaseInputStream, progressTaskUpdater) {
databaseKDB.masterKey = masterKey
}
databaseKDB
},
{ databaseInputStream ->
DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream,
masterKey,
tempCipherKey,
progressTaskUpdater)
val databaseKDBX = DatabaseKDBX()
mDatabaseKDBX?.let {
databaseKDBX.binaryCache = it.binaryCache
}
DatabaseInputKDBX(databaseKDBX).apply {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
openDatabase(databaseInputStream, progressTaskUpdater) {
databaseKDBX.masterKey = masterKey
}
}
databaseKDBX
}
)
} ?: run {
Log.e(TAG, "Database URI is null, database cannot be reloaded")
throw IODatabaseException()
} else {
throw IODatabaseException("Database URI is null, database cannot be reloaded")
}
} catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to load keyfile", e)
throw FileNotFoundDatabaseException()
throw FileNotFoundDatabaseException("Unable to load the keyfile")
} catch (e: LoadDatabaseException) {
throw e
} catch (e: Exception) {
throw LoadDatabaseException(e)
} finally {
dataModifiedSinceLastLoading = false
}
}
fun isGroupSearchable(group: Group, omitBackup: Boolean): Boolean {
return mDatabaseKDB?.isGroupSearchable(group.groupKDB, omitBackup) ?:
mDatabaseKDBX?.isGroupSearchable(group.groupKDBX, omitBackup) ?:
false
fun groupIsInRecycleBin(group: Group): Boolean {
val groupKDB = group.groupKDB
val groupKDBX = group.groupKDBX
if (groupKDB != null) {
return mDatabaseKDB?.isInRecycleBin(groupKDB) ?: false
} else if (groupKDBX != null) {
return mDatabaseKDBX?.isInRecycleBin(groupKDBX) ?: false
}
return false
}
fun createVirtualGroupFromSearch(searchQuery: String,
omitBackup: Boolean,
fun groupIsInTemplates(group: Group): Boolean {
val groupKDBX = group.groupKDBX
if (groupKDBX != null) {
return mDatabaseKDBX?.getTemplatesGroup() == groupKDBX
}
return false
}
fun createVirtualGroupFromSearch(searchParameters: SearchParameters,
fromGroup: NodeId<*>? = null,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
SearchParameters().apply {
this.searchQuery = searchQuery
}, omitBackup, max)
searchParameters, fromGroup, max)
}
fun createVirtualGroupFromSearchInfo(searchInfoString: String,
omitBackup: Boolean,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
SearchParameters().apply {
searchQuery = searchInfoString
searchInTitles = true
searchInUserNames = false
searchInUsernames = false
searchInPasswords = false
searchInUrls = true
searchInNotes = true
@@ -697,13 +863,21 @@ class Database {
searchInOther = true
searchInUUIDs = false
searchInTags = false
searchInCurrentGroup = false
searchInSearchableGroup = true
searchInRecycleBin = false
searchInTemplates = false
}, omitBackup, max)
}, null, max)
}
val tagPool: Tags
get() {
return mDatabaseKDBX?.tagPool ?: Tags()
}
val attachmentPool: AttachmentPool
get() {
return mDatabaseKDB?.attachmentPool ?: mDatabaseKDBX?.attachmentPool ?: AttachmentPool(binaryCache)
return mDatabaseKDB?.attachmentPool ?: mDatabaseKDBX?.attachmentPool ?: AttachmentPool()
}
val allowMultipleAttachments: Boolean
@@ -716,8 +890,8 @@ class Database {
}
fun buildNewBinaryAttachment(): BinaryData? {
return mDatabaseKDB?.buildNewAttachment()
?: mDatabaseKDBX?.buildNewAttachment( false,
return mDatabaseKDB?.buildNewBinaryAttachment()
?: mDatabaseKDBX?.buildNewBinaryAttachment( false,
compressionForNewEntry(),
false)
}
@@ -731,25 +905,16 @@ class Database {
fun removeUnlinkedAttachments() {
// No check in database KDB because unique attachment by entry
mDatabaseKDBX?.removeUnlinkedAttachments(true)
dataModifiedSinceLastLoading = true
}
@Throws(DatabaseOutputException::class)
fun saveData(contentResolver: ContentResolver) {
fun saveData(databaseCopyUri: Uri?, contentResolver: ContentResolver) {
try {
this.fileUri?.let {
saveData(contentResolver, it)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to save database", e)
throw DatabaseOutputException(e)
}
}
@Throws(IOException::class, DatabaseOutputException::class)
private fun saveData(contentResolver: ContentResolver, uri: Uri) {
if (uri.scheme == "file") {
uri.path?.let { filename ->
val saveUri = databaseCopyUri ?: this.fileUri
if (saveUri != null) {
if (saveUri.scheme == "file") {
saveUri.path?.let { filename ->
val tempFile = File("$filename.tmp")
var fileOutputStream: FileOutputStream? = null
@@ -778,10 +943,16 @@ class Database {
} else {
var outputStream: OutputStream? = null
try {
outputStream = contentResolver.openOutputStream(uri, "rwt")
outputStream = contentResolver.openOutputStream(saveUri, "rwt")
outputStream?.let { definedOutputStream ->
val databaseOutput = mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, definedOutputStream) }
val databaseOutput =
mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let {
DatabaseOutputKDBX(
it,
definedOutputStream
)
}
databaseOutput?.output()
}
} catch (e: Exception) {
@@ -790,17 +961,32 @@ class Database {
outputStream?.close()
}
}
this.fileUri = uri
if (databaseCopyUri == null) {
this.dataModifiedSinceLastLoading = false
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to save database", e)
throw DatabaseOutputException(e)
}
}
fun clear(filesDirectory: File? = null) {
binaryCache.clear()
iconsManager.clearCache()
fun clearIndexesAndBinaries(filesDirectory: File? = null) {
this.mDatabaseKDB?.clearIndexes()
this.mDatabaseKDBX?.clearIndexes()
this.mDatabaseKDB?.clearIconsCache()
this.mDatabaseKDBX?.clearIconsCache()
this.mDatabaseKDB?.clearAttachmentsCache()
this.mDatabaseKDBX?.clearAttachmentsCache()
this.mDatabaseKDB?.clearBinaries()
this.mDatabaseKDBX?.clearBinaries()
iconDrawableFactory.clearCache()
// Delete the cache of the database if present
mDatabaseKDB?.clearCache()
mDatabaseKDBX?.clearCache()
// In all cases, delete all the files in the temp dir
// delete all the files in the temp dir if allowed
try {
filesDirectory?.let { directory ->
cleanDirectory(directory)
@@ -811,7 +997,7 @@ class Database {
}
fun clearAndClose(context: Context? = null) {
clear(context?.let { UriUtil.getBinaryDir(context) })
clearIndexesAndBinaries(context?.let { UriUtil.getBinaryDir(context) })
this.mDatabaseKDB = null
this.mDatabaseKDBX = null
this.fileUri = null
@@ -838,9 +1024,10 @@ class Database {
}
@Throws(IOException::class)
fun retrieveMasterKey(key: String?, keyInputStream: InputStream?) {
fun assignMasterKey(key: String?, keyInputStream: InputStream?) {
mDatabaseKDB?.retrieveMasterKey(key, keyInputStream)
mDatabaseKDBX?.retrieveMasterKey(key, keyInputStream)
mDatabaseKDBX?.keyLastChanged = DateInstant()
}
fun rootCanContainsEntry(): Boolean {
@@ -848,6 +1035,7 @@ class Database {
}
fun createEntry(): Entry? {
dataModifiedSinceLastLoading = true
mDatabaseKDB?.let { database ->
return Entry(database.createEntry()).apply {
nodeId = database.newEntryId()
@@ -862,19 +1050,25 @@ class Database {
return null
}
fun createGroup(): Group? {
fun createGroup(virtual: Boolean = false): Group? {
if (!virtual) {
dataModifiedSinceLastLoading = true
}
var group: Group? = null
mDatabaseKDB?.let { database ->
return Group(database.createGroup()).apply {
group = Group(database.createGroup()).apply {
setNodeId(database.newGroupId())
}
}
mDatabaseKDBX?.let { database ->
return Group(database.createGroup()).apply {
group = Group(database.createGroup()).apply {
setNodeId(database.newGroupId())
}
}
if (virtual)
group?.isVirtual = virtual
return null
return group
}
fun getEntryById(id: NodeId<UUID>): Entry? {
@@ -900,6 +1094,7 @@ class Database {
}
fun addEntryTo(entry: Entry, parent: Group) {
dataModifiedSinceLastLoading = true
entry.entryKDB?.let { entryKDB ->
mDatabaseKDB?.addEntryTo(entryKDB, parent.groupKDB)
}
@@ -910,6 +1105,7 @@ class Database {
}
fun updateEntry(entry: Entry) {
dataModifiedSinceLastLoading = true
entry.entryKDB?.let { entryKDB ->
mDatabaseKDB?.updateEntry(entryKDB)
}
@@ -919,6 +1115,7 @@ class Database {
}
fun removeEntryFrom(entry: Entry, parent: Group) {
dataModifiedSinceLastLoading = true
entry.entryKDB?.let { entryKDB ->
mDatabaseKDB?.removeEntryFrom(entryKDB, parent.groupKDB)
}
@@ -929,6 +1126,7 @@ class Database {
}
fun addGroupTo(group: Group, parent: Group) {
dataModifiedSinceLastLoading = true
group.groupKDB?.let { groupKDB ->
mDatabaseKDB?.addGroupTo(groupKDB, parent.groupKDB)
}
@@ -939,6 +1137,7 @@ class Database {
}
fun updateGroup(group: Group) {
dataModifiedSinceLastLoading = true
group.groupKDB?.let { entryKDB ->
mDatabaseKDB?.updateGroup(entryKDB)
}
@@ -948,6 +1147,7 @@ class Database {
}
fun removeGroupFrom(group: Group, parent: Group) {
dataModifiedSinceLastLoading = true
group.groupKDB?.let { groupKDB ->
mDatabaseKDB?.removeGroupFrom(groupKDB, parent.groupKDB)
}
@@ -986,12 +1186,17 @@ class Database {
}
fun deleteEntry(entry: Entry) {
dataModifiedSinceLastLoading = true
entry.entryKDBX?.id?.let { entryId ->
mDatabaseKDBX?.addDeletedObject(entryId)
}
entry.parent?.let {
removeEntryFrom(entry, it)
}
}
fun deleteGroup(group: Group) {
dataModifiedSinceLastLoading = true
group.doForEachChildAndForIt(
object : NodeHandler<Entry>() {
override fun operate(node: Entry): Boolean {
@@ -1001,6 +1206,9 @@ class Database {
},
object : NodeHandler<Group>() {
override fun operate(node: Group): Boolean {
node.groupKDBX?.id?.let { groupId ->
mDatabaseKDBX?.addDeletedObject(groupId)
}
node.parent?.let {
removeGroupFrom(node, it)
}
@@ -1009,24 +1217,6 @@ class Database {
})
}
fun undoDeleteEntry(entry: Entry, parent: Group) {
entry.entryKDB?.let {
mDatabaseKDB?.undoDeleteEntryFrom(it, parent.groupKDB)
}
entry.entryKDBX?.let {
mDatabaseKDBX?.undoDeleteEntryFrom(it, parent.groupKDBX)
}
}
fun undoDeleteGroup(group: Group, parent: Group) {
group.groupKDB?.let {
mDatabaseKDB?.undoDeleteGroupFrom(it, parent.groupKDB)
}
group.groupKDBX?.let {
mDatabaseKDBX?.undoDeleteGroupFrom(it, parent.groupKDBX)
}
}
fun ensureRecycleBinExists(resources: Resources) {
mDatabaseKDB?.ensureBackupExists()
mDatabaseKDBX?.ensureRecycleBinExists(resources)
@@ -1055,47 +1245,41 @@ class Database {
}
fun recycle(entry: Entry, resources: Resources) {
entry.entryKDB?.let {
mDatabaseKDB?.recycle(it)
ensureRecycleBinExists(resources)
entry.parent?.let { parent ->
removeEntryFrom(entry, parent)
}
entry.entryKDBX?.let {
mDatabaseKDBX?.recycle(it, resources)
recycleBin?.let {
addEntryTo(entry, it)
}
entry.afterAssignNewParent()
}
fun recycle(group: Group, resources: Resources) {
group.groupKDB?.let {
mDatabaseKDB?.recycle(it)
ensureRecycleBinExists(resources)
group.parent?.let { parent ->
removeGroupFrom(group, parent)
}
group.groupKDBX?.let {
mDatabaseKDBX?.recycle(it, resources)
recycleBin?.let {
addGroupTo(group, it)
}
group.afterAssignNewParent()
}
fun undoRecycle(entry: Entry, parent: Group) {
entry.entryKDB?.let { entryKDB ->
parent.groupKDB?.let { parentKDB ->
mDatabaseKDB?.undoRecycle(entryKDB, parentKDB)
}
}
entry.entryKDBX?.let { entryKDBX ->
parent.groupKDBX?.let { parentKDBX ->
mDatabaseKDBX?.undoRecycle(entryKDBX, parentKDBX)
}
recycleBin?.let { it ->
removeEntryFrom(entry, it)
}
addEntryTo(entry, parent)
entry.afterAssignNewParent()
}
fun undoRecycle(group: Group, parent: Group) {
group.groupKDB?.let { groupKDB ->
parent.groupKDB?.let { parentKDB ->
mDatabaseKDB?.undoRecycle(groupKDB, parentKDB)
}
}
group.groupKDBX?.let { entryKDBX ->
parent.groupKDBX?.let { parentKDBX ->
mDatabaseKDBX?.undoRecycle(entryKDBX, parentKDBX)
}
recycleBin?.let {
removeGroupFrom(group, it)
}
addGroupTo(group, parent)
group.afterAssignNewParent()
}
fun startManageEntry(entry: Entry?) {
@@ -1117,6 +1301,18 @@ class Database {
return mDatabaseKDBX != null
}
fun allowCustomSearchableGroup(): Boolean {
return mDatabaseKDBX != null
}
fun allowAutoType(): Boolean {
return mDatabaseKDBX != null
}
fun allowTags(): Boolean {
return mDatabaseKDBX != null
}
/**
* Remove oldest history for each entry if more than max items or max memory
*/

View File

@@ -28,29 +28,18 @@ import java.util.*
class DeletedObject : Parcelable {
var uuid: UUID = DatabaseVersioned.UUID_ZERO
private var mDeletionTime: DateInstant? = null
var deletionTime: DateInstant = DateInstant()
constructor()
constructor(uuid: UUID, deletionTime: DateInstant = DateInstant()) {
this.uuid = uuid
this.mDeletionTime = deletionTime
this.deletionTime = deletionTime
}
constructor(parcel: Parcel) {
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
mDeletionTime = parcel.readParcelable(DateInstant::class.java.classLoader)
}
fun getDeletionTime(): DateInstant {
if (mDeletionTime == null) {
mDeletionTime = DateInstant(System.currentTimeMillis())
}
return mDeletionTime!!
}
fun setDeletionTime(deletionTime: DateInstant) {
this.mDeletionTime = deletionTime
deletionTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: deletionTime
}
override fun equals(other: Any?): Boolean {
@@ -69,7 +58,7 @@ class DeletedObject : Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(ParcelUuid(uuid), flags)
parcel.writeParcelable(mDeletionTime, flags)
parcel.writeParcelable(deletionTime, flags)
}
override fun describeContents(): Int {

View File

@@ -26,6 +26,7 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.AutoType
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
@@ -276,6 +277,18 @@ class Entry : Node, EntryVersionedInterface<Group> {
}
}
var customData: CustomData
get() = entryKDBX?.customData ?: CustomData()
set(value) {
entryKDBX?.customData = value
}
var autoType: AutoType
get() = entryKDBX?.autoType ?: AutoType()
set(value) {
entryKDBX?.autoType = value
}
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
@@ -457,8 +470,11 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryInfo.expiryTime = expiryTime
entryInfo.url = url
entryInfo.notes = notes
entryInfo.tags = tags
entryInfo.backgroundColor = backgroundColor
entryInfo.foregroundColor = foregroundColor
entryInfo.customData = customData
entryInfo.autoType = autoType
entryInfo.customFields = getExtraFields().toMutableList()
// Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel
@@ -493,8 +509,11 @@ class Entry : Node, EntryVersionedInterface<Group> {
expiryTime = newEntryInfo.expiryTime
url = newEntryInfo.url
notes = newEntryInfo.notes
tags = newEntryInfo.tags
backgroundColor = newEntryInfo.backgroundColor
foregroundColor = newEntryInfo.foregroundColor
customData = newEntryInfo.customData
autoType = newEntryInfo.autoType
addExtraFields(newEntryInfo.customFields)
database?.attachmentPool?.let { binaryPool ->
newEntryInfo.attachments.forEach { attachment ->

View File

@@ -31,7 +31,6 @@ import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UuidUtil
import java.util.*
import kotlin.collections.ArrayList
@@ -263,6 +262,12 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
}
}
var customData: CustomData
get() = groupKDBX?.customData ?: CustomData()
set(value) {
groupKDBX?.customData = value
}
override fun getChildGroups(): List<Group> {
return groupKDB?.getChildGroups()?.map {
Group(it)
@@ -435,12 +440,35 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDBX?.nodeId = id
}
fun setEnableAutoType(enableAutoType: Boolean?) {
groupKDBX?.enableAutoType = enableAutoType
var searchable: Boolean?
get() = groupKDBX?.enableSearching
set(value) {
groupKDBX?.enableSearching = value
}
fun setEnableSearching(enableSearching: Boolean?) {
groupKDBX?.enableSearching = enableSearching
fun isSearchable(): Boolean {
val searchableGroup = searchable
if (searchableGroup == null) {
val parenGroup = parent
if (parenGroup == null)
return true
else
return parenGroup.isSearchable()
} else {
return searchableGroup
}
}
var enableAutoType: Boolean?
get() = groupKDBX?.enableAutoType
set(value) {
groupKDBX?.enableAutoType = value
}
var defaultAutoTypeSequence: String
get() = groupKDBX?.defaultAutoTypeSequence ?: ""
set(value) {
groupKDBX?.defaultAutoTypeSequence = value
}
fun setExpanded(expanded: Boolean) {
@@ -463,6 +491,11 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupInfo.expires = expires
groupInfo.expiryTime = expiryTime
groupInfo.notes = notes
groupInfo.searchable = searchable
groupInfo.enableAutoType = enableAutoType
groupInfo.defaultAutoTypeSequence = defaultAutoTypeSequence
groupInfo.tags = tags
groupInfo.customData = customData
return groupInfo
}
@@ -475,6 +508,11 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
expires = groupInfo.expires
expiryTime = groupInfo.expiryTime
notes = groupInfo.notes
searchable = groupInfo.searchable
enableAutoType = groupInfo.enableAutoType
defaultAutoTypeSequence = groupInfo.defaultAutoTypeSequence
tags = groupInfo.tags
customData = groupInfo.customData
}
override fun equals(other: Any?): Boolean {

View File

@@ -2,15 +2,19 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
class Tags: Parcelable {
private val mTags = ArrayList<String>()
private val mTags = mutableListOf<String>()
constructor()
constructor(values: String): this() {
mTags.addAll(values.split(';'))
mTags.addAll(values
.split(DELIMITER, DELIMITER1)
.filter { it.removeSpaceChars().isNotEmpty() }
)
}
constructor(parcel: Parcel) : this() {
@@ -25,15 +29,55 @@ class Tags: Parcelable {
return 0
}
fun setTags(tags: Tags) {
mTags.clear()
mTags.addAll(tags.mTags)
}
fun get(position: Int): String {
return mTags[position]
}
fun put(tag: String) {
if (tag.removeSpaceChars().isNotEmpty() && !mTags.contains(tag))
mTags.add(tag)
}
fun put(tags: Tags) {
tags.mTags.forEach {
put(it)
}
}
fun isEmpty(): Boolean {
return mTags.isEmpty()
}
fun isNotEmpty(): Boolean {
return !isEmpty()
}
fun size(): Int {
return mTags.size
}
fun clear() {
mTags.clear()
}
fun toList(): List<String> {
return mTags
}
override fun toString(): String {
return mTags.joinToString(";")
return mTags.joinToString(DELIMITER.toString())
}
companion object CREATOR : Parcelable.Creator<Tags> {
const val DELIMITER= ','
const val DELIMITER1= ';'
val DELIMITERS = listOf(',', ';')
override fun createFromParcel(parcel: Parcel): Tags {
return Tags(parcel)
}

View File

@@ -19,7 +19,7 @@
*/
package com.kunzisoft.keepass.database.element.binary
class AttachmentPool(binaryCache: BinaryCache) : BinaryPool<Int>(binaryCache) {
class AttachmentPool : BinaryPool<Int>() {
/**
* Utility method to find an unused key in the pool

View File

@@ -180,12 +180,16 @@ abstract class BinaryData : Parcelable {
companion object {
private val TAG = BinaryData::class.java.name
private const val MAX_BINARY_BYTE = 10485760 // 10 MB
fun canMemoryBeAllocatedInRAM(context: Context, memoryWanted: Long): Boolean {
if (memoryWanted > MAX_BINARY_BYTE)
return false
val memoryInfo = ActivityManager.MemoryInfo()
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(memoryInfo)
(context.getSystemService(Context.ACTIVITY_SERVICE)
as? ActivityManager?)?.getMemoryInfo(memoryInfo)
val availableMemory = memoryInfo.availMem
return availableMemory > memoryWanted * 3
return availableMemory > (memoryWanted * 5)
}
}

View File

@@ -23,7 +23,7 @@ import android.util.Log
import java.io.IOException
import kotlin.math.abs
abstract class BinaryPool<T>(private val mBinaryCache: BinaryCache) {
abstract class BinaryPool<T> {
protected val pool = LinkedHashMap<T, BinaryData>()
@@ -225,9 +225,6 @@ abstract class BinaryPool<T>(private val mBinaryCache: BinaryCache) {
@Throws(IOException::class)
fun clear() {
doForEachBinary { _, binary ->
binary.clear(mBinaryCache)
}
pool.clear()
}

View File

@@ -4,19 +4,16 @@ import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import java.util.*
class CustomIconPool(private val binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
class CustomIconPool : BinaryPool<UUID>() {
private val customIcons = HashMap<UUID, IconImageCustom>()
fun put(key: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean,
builder: (uniqueBinaryId: String) -> BinaryData,
result: (IconImageCustom, BinaryData?) -> Unit) {
val keyBinary = super.put(key) { uniqueBinaryId ->
// Create a byte array for better performance with small data
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
}
val keyBinary = super.put(key, builder)
val uuid = keyBinary.keys.first()
val customIcon = IconImageCustom(uuid, name, lastModificationTime)
customIcons[uuid] = customIcon

View File

@@ -34,21 +34,44 @@ import com.kunzisoft.keepass.database.element.node.NodeVersioned
import java.io.IOException
import java.io.InputStream
import java.util.*
import kotlin.collections.ArrayList
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
override var encryptionAlgorithm: EncryptionAlgorithm = EncryptionAlgorithm.AESRijndael
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm> = listOf(
EncryptionAlgorithm.AESRijndael,
EncryptionAlgorithm.Twofish
)
override var kdfEngine: KdfEngine?
get() = kdfAvailableList[0]
set(value) {
value?.let {
numberKeyEncryptionRounds = value.defaultKeyRounds
}
}
override val kdfAvailableList: List<KdfEngine> = listOf(
KdfFactory.aesKdf
)
override val passwordEncoding: String
get() = "ISO-8859-1"
override var numberKeyEncryptionRounds = 300L
override val version: String
get() = "V1"
override val defaultFileExtension: String
get() = ".kdb"
init {
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
rootGroup = createGroup().apply {
icon.standard = getStandardIcon(IconImageStandard.DATABASE_ID)
}
kdfListV3.add(KdfFactory.aesKdf)
}
val backupGroup: GroupKDB?
@@ -65,29 +88,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
var color: Int? = null
override val kdfEngine: KdfEngine
get() = kdfListV3[0]
override val kdfAvailableList: List<KdfEngine>
get() = kdfListV3
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
get() {
val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael)
list.add(EncryptionAlgorithm.Twofish)
return list
}
override val passwordEncoding: String
get() = "ISO-8859-1"
override var numberKeyEncryptionRounds = 300L
init {
algorithm = EncryptionAlgorithm.AESRijndael
}
/**
* Generates an unused random tree id
*
@@ -212,29 +212,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return true
}
fun recycle(group: GroupKDB) {
removeGroupFrom(group, group.parent)
addGroupTo(group, backupGroup)
group.afterAssignNewParent()
}
fun recycle(entry: EntryKDB) {
removeEntryFrom(entry, entry.parent)
addEntryTo(entry, backupGroup)
entry.afterAssignNewParent()
}
fun undoRecycle(group: GroupKDB, origParent: GroupKDB) {
removeGroupFrom(group, backupGroup)
addGroupTo(group, origParent)
}
fun undoRecycle(entry: EntryKDB, origParent: GroupKDB) {
removeEntryFrom(entry, backupGroup)
addEntryTo(entry, origParent)
}
fun buildNewAttachment(): BinaryData {
fun buildNewBinaryAttachment(): BinaryData {
// Generate an unique new file
return attachmentPool.put { uniqueBinaryId ->
binaryCache.getBinaryData(uniqueBinaryId, false)

View File

@@ -25,16 +25,16 @@ import android.util.Log
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.AesEngine
import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
@@ -42,12 +42,13 @@ import com.kunzisoft.keepass.database.element.entry.FieldReferencesEngine
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateEngineCompatible
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_31
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41
@@ -66,6 +67,7 @@ import javax.crypto.Mac
import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
import kotlin.collections.HashSet
import kotlin.math.min
@@ -73,27 +75,70 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var hmacKey: ByteArray? = null
private set
var cipherUuid = EncryptionAlgorithm.AESRijndael.uuid
private var dataEngine: CipherEngine = AesEngine()
var compressionAlgorithm = CompressionAlgorithm.GZip
override var encryptionAlgorithm: EncryptionAlgorithm = EncryptionAlgorithm.AESRijndael
fun setEncryptionAlgorithmFromUUID(uuid: UUID) {
encryptionAlgorithm = EncryptionAlgorithm.getFrom(uuid)
}
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm> = listOf(
EncryptionAlgorithm.AESRijndael,
EncryptionAlgorithm.Twofish,
EncryptionAlgorithm.ChaCha20
)
var kdfParameters: KdfParameters? = null
private var kdfList: MutableList<KdfEngine> = ArrayList()
private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary()
override var kdfEngine: KdfEngine?
get() = getKdfEngineFromParameters(kdfParameters)
set(value) {
value?.let {
if (kdfParameters?.uuid != value.defaultParameters.uuid)
kdfParameters = value.defaultParameters
numberKeyEncryptionRounds = value.defaultKeyRounds
memoryUsage = value.defaultMemoryUsage
parallelism = value.defaultParallelism
}
}
private fun getKdfEngineFromParameters(kdfParameters: KdfParameters?): KdfEngine? {
if (kdfParameters == null) {
return null
}
for (engine in kdfAvailableList) {
if (engine.uuid == kdfParameters.uuid) {
return engine
}
}
return null
}
fun randomizeKdfParameters() {
kdfParameters?.let {
kdfEngine?.randomize(it)
}
}
override val kdfAvailableList: List<KdfEngine> = listOf(
KdfFactory.aesKdf,
KdfFactory.argon2dKdf,
KdfFactory.argon2idKdf
)
var compressionAlgorithm = CompressionAlgorithm.GZip
private val mFieldReferenceEngine = FieldReferencesEngine(this)
private val mTemplateEngine = TemplateEngineCompatible(this)
var kdbxVersion = UnsignedInt(0)
var name = ""
var nameChanged = DateInstant()
// TODO change setting date
var settingsChanged = DateInstant()
var description = ""
var descriptionChanged = DateInstant()
var defaultUserName = ""
var defaultUserNameChanged = DateInstant()
// TODO last change date
var settingsChanged = DateInstant()
var keyLastChanged = DateInstant()
var keyChangeRecDays: Long = -1
var keyChangeForceDays: Long = 1
@@ -115,16 +160,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var lastSelectedGroupUUID = UUID_ZERO
var lastTopVisibleGroupUUID = UUID_ZERO
var memoryProtection = MemoryProtectionConfig()
val deletedObjects = ArrayList<DeletedObject>()
val deletedObjects = HashSet<DeletedObject>()
var publicCustomData = VariantDictionary()
val customData = CustomData()
var localizedAppName = "KeePassDX"
val tagPool = Tags()
init {
kdfList.add(KdfFactory.aesKdf)
kdfList.add(KdfFactory.argon2dKdf)
kdfList.add(KdfFactory.argon2idKdf)
}
var localizedAppName = "KeePassDX"
constructor()
@@ -159,38 +201,75 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return "V2 - KDBX$kdbxStringVersion"
}
override val kdfEngine: KdfEngine?
get() = try {
getEngineKDBX4(kdfParameters)
} catch (unknownKDF: UnknownKDF) {
Log.i(TAG, "Unable to retrieve KDF engine", unknownKDF)
null
override val defaultFileExtension: String
get() = ".kdbx"
private open class NodeOperationHandler<T: NodeKDBXInterface> : NodeHandler<T>() {
var containsCustomData = false
override fun operate(node: T): Boolean {
if (node.customData.isNotEmpty()) {
containsCustomData = true
}
return true
}
}
override val kdfAvailableList: List<KdfEngine>
get() = kdfList
@Throws(UnknownKDF::class)
fun getEngineKDBX4(kdfParameters: KdfParameters?): KdfEngine {
val unknownKDFException = UnknownKDF()
if (kdfParameters == null) {
throw unknownKDFException
private inner class EntryOperationHandler: NodeOperationHandler<EntryKDBX>() {
var passwordQualityEstimationDisabled = false
override fun operate(node: EntryKDBX): Boolean {
if (!node.qualityCheck) {
passwordQualityEstimationDisabled = true
}
for (engine in kdfList) {
if (engine.uuid == kdfParameters.uuid) {
return engine
return super.operate(node)
}
}
throw unknownKDFException
}
val availableCompressionAlgorithms: List<CompressionAlgorithm>
get() {
val list = ArrayList<CompressionAlgorithm>()
list.add(CompressionAlgorithm.None)
list.add(CompressionAlgorithm.GZip)
return list
private inner class GroupOperationHandler: NodeOperationHandler<GroupKDBX>() {
var containsTags = false
override fun operate(node: GroupKDBX): Boolean {
if (node.tags.isNotEmpty())
containsTags = true
return super.operate(node)
}
}
fun getMinKdbxVersion(): UnsignedInt {
val entryHandler = EntryOperationHandler()
val groupHandler = GroupOperationHandler()
rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler)
// https://keepass.info/help/kb/kdbx_4.1.html
val containsGroupWithTag = groupHandler.containsTags
val containsEntryWithPasswordQualityEstimationDisabled = entryHandler.passwordQualityEstimationDisabled
val containsCustomIconWithNameOrLastModificationTime = iconsManager.containsCustomIconWithNameOrLastModificationTime()
val containsHeaderCustomDataWithLastModificationTime = customData.containsItemWithLastModificationTime()
// https://keepass.info/help/kb/kdbx_4.html
// If AES is not use, it's at least 4.0
val keyDerivationFunction = kdfEngine
val kdfIsNotAes = keyDerivationFunction != null && keyDerivationFunction.uuid != AesKdf.CIPHER_UUID
val containsHeaderCustomData = customData.isNotEmpty()
val containsNodeCustomData = entryHandler.containsCustomData || groupHandler.containsCustomData
// Check each condition to determine version
return if (containsGroupWithTag
|| containsEntryWithPasswordQualityEstimationDisabled
|| containsCustomIconWithNameOrLastModificationTime
|| containsHeaderCustomDataWithLastModificationTime) {
FILE_VERSION_41
} else if (kdfIsNotAes
|| containsHeaderCustomData
|| containsNodeCustomData) {
FILE_VERSION_40
} else {
FILE_VERSION_31
}
}
val availableCompressionAlgorithms: List<CompressionAlgorithm> = listOf(
CompressionAlgorithm.None,
CompressionAlgorithm.GZip
)
fun changeBinaryCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) {
@@ -245,18 +324,10 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
}
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
get() {
val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael)
list.add(EncryptionAlgorithm.Twofish)
list.add(EncryptionAlgorithm.ChaCha20)
return list
}
override var numberKeyEncryptionRounds: Long
get() {
val kdfEngine = kdfEngine
var numKeyEncRounds: Long = 0
if (kdfEngine != null && kdfParameters != null)
numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!)
return numKeyEncRounds
@@ -265,7 +336,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
kdfEngine.setKeyRounds(kdfParameters!!, rounds)
numKeyEncRounds = rounds
}
var memoryUsage: Long
@@ -305,7 +375,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Retrieve recycle bin in index
val recycleBin: GroupKDBX?
get() = if (recycleBinUUID == UUID_ZERO) null else getGroupByUUID(recycleBinUUID)
get() = getGroupByUUID(recycleBinUUID)
val lastSelectedGroup: GroupKDBX?
get() = getGroupByUUID(lastSelectedGroupUUID)
@@ -313,17 +383,14 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val lastTopVisibleGroup: GroupKDBX?
get() = getGroupByUUID(lastTopVisibleGroupUUID)
fun setDataEngine(dataEngine: CipherEngine) {
this.dataEngine = dataEngine
}
override fun getStandardIcon(iconId: Int): IconImageStandard {
return this.iconsManager.getIcon(iconId)
}
fun buildNewCustomIcon(customIconId: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.buildNewCustomIcon(customIconId, result)
// Create a binary file for a brand new custom icon
addCustomIcon(customIconId, "", null, false, result)
}
fun addCustomIcon(customIconId: UUID? = null,
@@ -331,14 +398,21 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
lastModificationTime: DateInstant?,
smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.addCustomIcon(customIconId, name, lastModificationTime, smallSize, result)
iconsManager.addCustomIcon(customIconId, name, lastModificationTime, { uniqueBinaryId ->
// Create a byte array for better performance with small data
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
}, result)
}
fun removeCustomIcon(iconUuid: UUID) {
iconsManager.removeCustomIcon(iconUuid, binaryCache)
}
fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean {
return iconsManager.isCustomIconBinaryDuplicate(binary)
}
fun getCustomIcon(iconUuid: UUID): IconImageCustom {
fun getCustomIcon(iconUuid: UUID): IconImageCustom? {
return this.iconsManager.getIcon(iconUuid)
}
@@ -355,7 +429,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val templatesGroup = firstGroupWithValidName
?: mTemplateEngine.createNewTemplatesGroup(templatesGroupName)
entryTemplatesGroup = templatesGroup.id
entryTemplatesGroupChanged = templatesGroup.lastModificationTime
} else {
removeTemplatesGroup()
}
@@ -363,7 +436,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
fun removeTemplatesGroup() {
entryTemplatesGroup = UUID_ZERO
entryTemplatesGroupChanged = DateInstant()
mTemplateEngine.clearCache()
}
@@ -476,7 +548,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
fun makeFinalKey(masterSeed: ByteArray) {
kdfParameters?.let { keyDerivationFunctionParameters ->
val kdfEngine = getEngineKDBX4(keyDerivationFunctionParameters)
val kdfEngine = getKdfEngineFromParameters(keyDerivationFunctionParameters)
?: throw IOException("Unknown key derivation function")
var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters)
if (transformedMasterKey.size != 32) {
@@ -486,7 +559,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val cmpKey = ByteArray(65)
System.arraycopy(masterSeed, 0, cmpKey, 0, 32)
System.arraycopy(transformedMasterKey, 0, cmpKey, 32, 32)
finalKey = resizeKey(cmpKey, dataEngine.keyLength())
finalKey = resizeKey(cmpKey, encryptionAlgorithm.cipherEngine.keyLength())
val messageDigest: MessageDigest
try {
@@ -724,14 +797,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
firstGroupWithValidName
}
recycleBinUUID = recycleBinGroup.id
recycleBinChanged = recycleBinGroup.lastModificationTime
recycleBinChanged = DateInstant()
}
}
fun removeRecycleBin() {
if (recycleBin != null) {
recycleBinUUID = UUID_ZERO
recycleBinChanged = DateInstant()
}
}
@@ -753,64 +825,41 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return false
}
fun recycle(group: GroupKDBX, resources: Resources) {
ensureRecycleBinExists(resources)
removeGroupFrom(group, group.parent)
addGroupTo(group, recycleBin)
group.afterAssignNewParent()
}
fun recycle(entry: EntryKDBX, resources: Resources) {
ensureRecycleBinExists(resources)
removeEntryFrom(entry, entry.parent)
addEntryTo(entry, recycleBin)
entry.afterAssignNewParent()
}
fun undoRecycle(group: GroupKDBX, origParent: GroupKDBX) {
removeGroupFrom(group, recycleBin)
addGroupTo(group, origParent)
}
fun undoRecycle(entry: EntryKDBX, origParent: GroupKDBX) {
removeEntryFrom(entry, recycleBin)
addEntryTo(entry, origParent)
}
fun getDeletedObjects(): List<DeletedObject> {
return deletedObjects
fun getDeletedObject(nodeId: NodeId<UUID>): DeletedObject? {
return deletedObjects.find { it.uuid == nodeId.id }
}
fun addDeletedObject(deletedObject: DeletedObject) {
this.deletedObjects.add(deletedObject)
}
fun addDeletedObject(objectId: UUID) {
addDeletedObject(DeletedObject(objectId))
}
override fun addEntryTo(newEntry: EntryKDBX, parent: GroupKDBX?) {
super.addEntryTo(newEntry, parent)
tagPool.put(newEntry.tags)
mFieldReferenceEngine.clear()
}
override fun updateEntry(entry: EntryKDBX) {
super.updateEntry(entry)
tagPool.put(entry.tags)
mFieldReferenceEngine.clear()
}
override fun removeEntryFrom(entryToRemove: EntryKDBX, parent: GroupKDBX?) {
super.removeEntryFrom(entryToRemove, parent)
deletedObjects.add(DeletedObject(entryToRemove.id))
// Do not remove tags from pool, it's only in temp memory
mFieldReferenceEngine.clear()
}
override fun undoDeleteEntryFrom(entry: EntryKDBX, origParent: GroupKDBX?) {
super.undoDeleteEntryFrom(entry, origParent)
deletedObjects.remove(DeletedObject(entry.id))
}
fun containsPublicCustomData(): Boolean {
return publicCustomData.size() > 0
}
fun buildNewAttachment(smallSize: Boolean,
fun buildNewBinaryAttachment(smallSize: Boolean,
compression: Boolean,
protection: Boolean,
binaryPoolId: Int? = null): BinaryData {
@@ -830,6 +879,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
private fun removeUnlinkedAttachments(binaries: List<BinaryData>, clear: Boolean) {
// TODO check in icon pool
// Build binaries to remove with all binaries known
val binariesToRemove = ArrayList<BinaryData>()
if (binaries.isEmpty()) {
@@ -866,11 +916,10 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return super.validatePasswordEncoding(password, containsKeyFile)
}
override fun clearCache() {
override fun clearIndexes() {
try {
super.clearCache()
super.clearIndexes()
mFieldReferenceEngine.clear()
attachmentPool.clear()
} catch (e: Exception) {
Log.e(TAG, "Unable to clear cache", e)
}

View File

@@ -19,8 +19,10 @@
*/
package com.kunzisoft.keepass.database.element.database
import android.util.Log
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
@@ -44,47 +46,38 @@ abstract class DatabaseVersioned<
Entry : EntryVersioned<GroupId, EntryId, Group, Entry>
> {
// Algorithm used to encrypt the database
protected var algorithm: EncryptionAlgorithm? = null
abstract var encryptionAlgorithm: EncryptionAlgorithm
abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
abstract val kdfEngine: com.kunzisoft.keepass.database.crypto.kdf.KdfEngine?
abstract var kdfEngine: KdfEngine?
abstract val kdfAvailableList: List<KdfEngine>
abstract var numberKeyEncryptionRounds: Long
abstract val kdfAvailableList: List<com.kunzisoft.keepass.database.crypto.kdf.KdfEngine>
protected abstract val passwordEncoding: String
var masterKey = ByteArray(32)
var finalKey: ByteArray? = null
protected set
abstract val version: String
abstract val defaultFileExtension: String
/**
* To manage binaries in faster way
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var binaryCache = BinaryCache()
val iconsManager = IconsManager(binaryCache)
var attachmentPool = AttachmentPool(binaryCache)
var iconsManager = IconsManager()
var attachmentPool = AttachmentPool()
var changeDuplicateId = false
private var groupIndexes = LinkedHashMap<NodeId<GroupId>, Group>()
private var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
abstract val version: String
protected abstract val passwordEncoding: String
abstract var numberKeyEncryptionRounds: Long
var encryptionAlgorithm: EncryptionAlgorithm
get() {
return algorithm ?: EncryptionAlgorithm.AESRijndael
}
set(algorithm) {
this.algorithm = algorithm
}
abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
var rootGroup: Group? = null
set(value) {
field = value
@@ -274,7 +267,7 @@ abstract class DatabaseVersioned<
this.entryIndexes.remove(entry.nodeId)
}
open fun clearCache() {
open fun clearIndexes() {
this.groupIndexes.clear()
this.entryIndexes.clear()
}
@@ -304,7 +297,7 @@ abstract class DatabaseVersioned<
}
}
fun removeGroupFrom(groupToRemove: Group, parent: Group?) {
open fun removeGroupFrom(groupToRemove: Group, parent: Group?) {
// Remove tree from parent tree
parent?.removeChildGroup(groupToRemove)
removeGroupIndex(groupToRemove)
@@ -331,23 +324,39 @@ abstract class DatabaseVersioned<
removeEntryIndex(entryToRemove)
}
// TODO Delete group
fun undoDeleteGroupFrom(group: Group, origParent: Group?) {
addGroupTo(group, origParent)
}
open fun undoDeleteEntryFrom(entry: Entry, origParent: Group?) {
addEntryTo(entry, origParent)
}
abstract fun isInRecycleBin(group: Group): Boolean
fun isGroupSearchable(group: Group?, omitBackup: Boolean): Boolean {
if (group == null)
return false
if (omitBackup && isInRecycleBin(group))
return false
return true
fun clearIconsCache() {
iconsManager.doForEachCustomIcon { _, binary ->
try {
binary.clear(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to clear icon binary cache", e)
}
}
iconsManager.clear()
}
fun clearAttachmentsCache() {
attachmentPool.doForEachBinary { _, binary ->
try {
binary.clear(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to clear attachment binary cache", e)
}
}
attachmentPool.clear()
}
fun clearBinaries() {
binaryCache.clear()
}
fun clearAll() {
clearIndexes()
clearIconsCache()
clearAttachmentsCache()
clearBinaries()
}
companion object {

View File

@@ -139,8 +139,9 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeInt(binaryDataId ?: -1)
}
fun updateWith(source: EntryKDB) {
super.updateWith(source)
fun updateWith(source: EntryKDB,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
title = source.title
username = source.username
password = source.password

View File

@@ -110,8 +110,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
* Update with deep copy of each entry element
* @param source
*/
fun updateWith(source: EntryKDBX, copyHistory: Boolean = true) {
super.updateWith(source)
fun updateWith(source: EntryKDBX,
copyHistory: Boolean = true,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
customData = CustomData(source.customData)

View File

@@ -53,8 +53,9 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeInt(groupFlags)
}
fun updateWith(source: GroupKDB) {
super.updateWith(source)
fun updateWith(source: GroupKDB,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
groupFlags = source.groupFlags
}

View File

@@ -41,9 +41,9 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override var customData = CustomData()
var notes = ""
var isExpanded = true
var defaultAutoTypeSequence = ""
var enableAutoType: Boolean? = null
var enableSearching: Boolean? = null
var enableAutoType: Boolean? = null
var defaultAutoTypeSequence: String = ""
var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO
override var tags = Tags()
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
@@ -69,11 +69,11 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
val isAutoTypeEnabled = parcel.readInt()
enableAutoType = if (isAutoTypeEnabled == -1) null else isAutoTypeEnabled == 1
val isSearchingEnabled = parcel.readInt()
enableSearching = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1
val isAutoTypeEnabled = parcel.readInt()
enableAutoType = if (isAutoTypeEnabled == -1) null else isAutoTypeEnabled == 1
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
lastTopVisibleEntry = parcel.readSerializable() as UUID
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
previousParentGroup = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
@@ -94,25 +94,26 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
dest.writeParcelable(customData, flags)
dest.writeString(notes)
dest.writeByte((if (isExpanded) 1 else 0).toByte())
dest.writeString(defaultAutoTypeSequence)
dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
dest.writeInt(if (enableSearching == null) -1 else if (enableSearching!!) 1 else 0)
dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
dest.writeString(defaultAutoTypeSequence)
dest.writeSerializable(lastTopVisibleEntry)
dest.writeParcelable(tags, flags)
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
}
fun updateWith(source: GroupKDBX) {
super.updateWith(source)
fun updateWith(source: GroupKDBX,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map
customData = CustomData(source.customData)
notes = source.notes
isExpanded = source.isExpanded
defaultAutoTypeSequence = source.defaultAutoTypeSequence
enableAutoType = source.enableAutoType
enableSearching = source.enableSearching
enableAutoType = source.enableAutoType
defaultAutoTypeSequence = source.defaultAutoTypeSequence
lastTopVisibleEntry = source.lastTopVisibleEntry
tags = source.tags
previousParentGroup = source.previousParentGroup

View File

@@ -51,13 +51,16 @@ abstract class GroupVersioned
dest.writeString(titleGroup)
}
protected fun updateWith(source: GroupVersioned<GroupId, EntryId, Group, Entry>) {
super.updateWith(source)
protected fun updateWith(source: GroupVersioned<GroupId, EntryId, Group, Entry>,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
titleGroup = source.titleGroup
if (updateParents) {
removeChildren()
childGroups.addAll(source.childGroups)
childEntries.addAll(source.childEntries)
}
}
override var title: String
get() = titleGroup

View File

@@ -28,12 +28,12 @@ import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.K
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.util.*
class IconsManager(binaryCache: BinaryCache) {
class IconsManager {
private val standardCache = List(NB_ICONS) {
IconImageStandard(it)
}
private val customCache = CustomIconPool(binaryCache)
private val customCache = CustomIconPool()
fun getIcon(iconId: Int): IconImageStandard {
val searchIconId = if (IconImageStandard.isCorrectIconId(iconId)) iconId else KEY_ID
@@ -50,29 +50,23 @@ class IconsManager(binaryCache: BinaryCache) {
* Custom
*/
fun buildNewCustomIcon(key: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) {
// Create a binary file for a brand new custom icon
addCustomIcon(key, "", null, false, result)
}
fun addCustomIcon(key: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean,
builder: (uniqueBinaryId: String) -> BinaryData,
result: (IconImageCustom, BinaryData?) -> Unit) {
customCache.put(key, name, lastModificationTime, smallSize, result)
customCache.put(key, name, lastModificationTime, builder, result)
}
fun getIcon(iconUuid: UUID): IconImageCustom {
return customCache.getCustomIcon(iconUuid) ?: IconImageCustom(iconUuid)
fun getIcon(iconUuid: UUID): IconImageCustom? {
return customCache.getCustomIcon(iconUuid)
}
fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean {
return customCache.isBinaryDuplicate(binaryData)
}
fun removeCustomIcon(binaryCache: BinaryCache, iconUuid: UUID) {
fun removeCustomIcon(iconUuid: UUID, binaryCache: BinaryCache) {
val binary = customCache[iconUuid]
customCache.remove(iconUuid)
try {
@@ -99,12 +93,8 @@ class IconsManager(binaryCache: BinaryCache) {
/**
* Clear the cache of icons
*/
fun clearCache() {
try {
fun clear() {
customCache.clear()
} catch(e: Exception) {
Log.e(TAG, "Unable to clear cache", e)
}
}
companion object {

View File

@@ -68,9 +68,12 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
return 0
}
protected fun updateWith(source: NodeVersioned<IdType, Parent, Entry>) {
protected fun updateWith(source: NodeVersioned<IdType, Parent, Entry>,
updateParents: Boolean = true) {
this.nodeId = copyNodeId(source.nodeId)
if (updateParents) {
this.parent = source.parent
}
this.icon = source.icon
this.creationTime = DateInstant(source.creationTime)
this.lastModificationTime = DateInstant(source.lastModificationTime)

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