Compare commits

..

368 Commits

Author SHA1 Message Date
J-Jamet
ecc4550261 Add Deutsh description 2022-09-07 22:42:18 +02:00
J-Jamet
8b046512e3 fix: upgrade Gemfile.lock 2022-09-04 14:20:36 +02:00
J-Jamet
228a10c8e0 Merge branch 'translations' into develop 2022-09-04 12:24:32 +02:00
J-Jamet
9c53bea190 fix: replace <strong> tags 2022-09-04 12:23:49 +02:00
J-Jamet
11cf991498 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2022-09-04 12:15:55 +02:00
J-Jamet
a88c3721b2 Merge branch 'translations' into develop 2022-09-04 12:14:33 +02:00
J-Jamet
0b4b6d4d91 feat: upgrade to 3.5.0 2022-09-04 12:13:19 +02:00
J-Jamet
941f9bcd48 fix: Change key driver url 2022-09-03 23:03:11 +02:00
solokot
f1bf9fb25c Translated using Weblate (Russian)
Currently translated at 99.5% (628 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-09-03 19:24:05 +02:00
Matthaiks
1751fa49c0 Translated using Weblate (Polish)
Currently translated at 99.6% (629 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-09-03 19:24:05 +02:00
Kunzisoft
6b4fc9a4fa Translated using Weblate (French)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-09-03 19:24:04 +02:00
Retrial
7c8d85e428 Translated using Weblate (Greek)
Currently translated at 99.5% (628 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-09-03 19:24:04 +02:00
Allan Nordhøy
e335140f23 Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:24:03 +02:00
Kunzisoft
d85f398b5f Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:24:03 +02:00
Wilker Santana da Silva
a16082a59d Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:11:24 +02:00
Kunzisoft
456269a343 Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:11:23 +02:00
J-Jamet
eb8e1e20eb fix: Remove cancel button for development dialog 2022-09-03 17:30:31 +02:00
Hosted Weblate
ed3c84fec0 Merge branch 'origin/develop' into Weblate. 2022-09-03 17:27:29 +02:00
J-Jamet
be40416a2d feat: Add privacy text in About section 2022-09-03 17:25:20 +02:00
PiQuark6046
5b5476a513 Translated using Weblate (Korean)
Currently translated at 35.0% (221 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2022-09-01 19:19:15 +02:00
random r
dc64dd6400 Translated using Weblate (Italian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-09-01 19:19:14 +02:00
atilluF
eca02d3bde Translated using Weblate (Italian)
Currently translated at 97.4% (614 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-08-31 15:15:20 +02:00
SC
176b6c2936 Translated using Weblate (Portuguese)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-08-26 20:21:24 +02:00
J-Jamet
5b22350bdf fix: Hide clipboard text when copy entry field #1386 2022-08-23 11:56:29 +02:00
J-Jamet
6e1e011234 fix: exec gradlew version 7.5.1 to update scripts 2022-08-23 11:32:10 +02:00
J-Jamet
ac65ef6a5c Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2022-08-23 11:26:35 +02:00
Jérémy JAMET
fc198dde74 Merge pull request #1387 from lberrymage/update-gradle
Upgrade Gradle to 7.5.1
2022-08-23 11:25:59 +02:00
lberrymage
15ac51b2fc Upgrade Gradle to 7.5.1
Generated by `./gradlew wrapper --gradle-version 7.5.1`
2022-08-22 18:09:13 -08:00
J-Jamet
34214432e1 fix: upgrade libs 2022-08-17 23:01:45 +02:00
J-Jamet
361ca92493 fix: upgrade gradle plugin to 7.2.2 2022-08-17 22:56:50 +02:00
J-Jamet
e367051b80 fix: remove application/octet-stream file recognition #1211 2022-08-17 22:25:47 +02:00
Linerly
a2a4a50c5e Translated using Weblate (Indonesian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-08-14 14:20:41 +02:00
Milo Ivir
afc74b2f2a Translated using Weblate (Croatian)
Currently translated at 99.2% (625 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-08-14 14:20:40 +02:00
devchung
fc756d1eaf Translated using Weblate (Chinese (Traditional))
Currently translated at 97.3% (613 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2022-08-14 14:20:39 +02:00
Eric
eb8a4b1e49 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-08-14 14:20:39 +02:00
Ihor Hordiichuk
8d258b3538 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-08-14 14:20:38 +02:00
solokot
a59cfa3477 Translated using Weblate (Russian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-08-14 14:20:38 +02:00
Matthaiks
f1e513006e Translated using Weblate (Polish)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-08-14 14:20:37 +02:00
Retrial
9df5c8f439 Translated using Weblate (Greek)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-08-14 14:20:36 +02:00
Deleted User
3ae099accf Translated using Weblate (German)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-08-14 14:20:36 +02:00
VfBFan
bb3e9396f2 Translated using Weblate (German)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-08-14 14:20:35 +02:00
Hosted Weblate
1628749bde Merge branch 'origin/develop' into Weblate. 2022-08-11 12:47:58 +02:00
J-Jamet
f2006b5e42 fix: show lock button hidden by screenshot mode banner #1377 2022-08-11 12:37:28 +02:00
GianpaMX
80d387d9e7 Add screenshot mode
* Add new screenshot mode entry under Settings -> App -> General
* Disable Screenshot mode  by default
* Add a screenshot mode indication at the bottom of the screen
* Set or clear window FLAG_SECURE accordingly
* Translate strings into Spanish
2022-08-10 15:19:09 +01:00
J-Jamet
4452b4d599 Merge branch 'feature/Hardware_Key' into develop 2022-08-08 14:00:21 +02:00
J-Jamet
dfeaeb9888 feature: todo open external app in f-droid 2022-08-08 13:57:51 +02:00
J-Jamet
7e45a20ee7 fix: Refactoring key driver app id 2022-08-07 23:27:59 +02:00
J-Jamet
f3fe92e4de Merge branch 'develop' into feature/Hardware_Key 2022-08-02 22:25:44 +02:00
J-Jamet
b606909c65 fix: Update libs and SDK to 32 2022-08-02 22:25:19 +02:00
J-Jamet
2882bb30d7 fix: Smaller advanced unlock UI 2022-08-02 21:47:33 +02:00
eamz8jpajok
5b62227e3f Translated using Weblate (German)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-07-28 23:21:09 +02:00
J-Jamet
8b6af6fd8a feat: Derive master key exception 2022-07-05 18:09:35 +02:00
J-Jamet
99e9a92953 fix: KDB opening 2022-07-05 17:55:12 +02:00
Noël Krähenbühl
9f626309c3 Translated using Weblate (English (United Kingdom))
Currently translated at 8.3% (51 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en_GB/
2022-06-25 17:16:13 +02:00
Anonimas
3fe7cf2bfd Translated using Weblate (Lithuanian)
Currently translated at 23.4% (143 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2022-06-19 16:16:36 +02:00
Matthaiks
9b5c274b49 Translated using Weblate (Polish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-06-18 00:19:02 +02:00
WB
46b350e7ac Translated using Weblate (Galician)
Currently translated at 23.4% (143 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-06-16 00:19:34 +02:00
Óscar Fernández Díaz
22a4aeb108 Translated using Weblate (Spanish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-06-14 00:19:14 +02:00
random r
332e116ba7 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-06-04 11:15:25 +02:00
Anonimas
8b594a1a1f Translated using Weblate (Lithuanian)
Currently translated at 22.9% (140 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2022-06-02 18:17:01 +02:00
J-Jamet
ab23ec6d4d Merge tag '3.4.5' into develop
3.4.5
2022-06-02 11:29:08 +02:00
J-Jamet
0ef574d675 Merge branch 'release/3.4.5' 2022-06-02 11:29:00 +02:00
J-Jamet
6d15a2462d Merge branch 'translations' into develop 2022-06-02 11:02:57 +02:00
J-Jamet
24fcdeb7aa Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-06-02 11:01:11 +02:00
J-Jamet
13905db732 fix: searchable selection 2022-06-01 14:15:41 +02:00
J-Jamet
6e1c8e5bec fix: custom data in group (fix KeeShare) #1335 2022-06-01 12:41:01 +02:00
J-Jamet
9aa1d11b94 Change the order of the search filters 2022-05-31 18:46:46 +02:00
J-Jamet
6c9f359fae New clipboard manager #1343 2022-05-31 18:22:01 +02:00
J-Jamet
531ebcae85 Fix device credential unlocking #1344 2022-05-31 11:44:42 +02:00
J-Jamet
fe9601b510 Change to 3.4.5 2022-05-31 10:25:34 +02:00
Oymate
bdf7cc6ea0 Translated using Weblate (Bengali)
Currently translated at 15.7% (96 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn/
2022-05-30 21:18:06 +02:00
Douglas Han
1cfe02af6f Translated using Weblate (Korean)
Currently translated at 28.0% (171 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2022-05-30 21:18:05 +02:00
J-Jamet
647e3f9383 Change intent challenge recognition 2022-05-30 18:19:01 +02:00
J-Jamet
597f52799d Cache to capture exception during database save 2022-05-30 16:59:33 +02:00
J-Jamet
a59e052ed8 Merge branch 'develop' into feature/Hardware_Key 2022-05-30 10:31:46 +02:00
J-Jamet
11da0a4500 Keep screen on by default when viewing an entry
Upgrade to 3.5.0
2022-05-30 10:31:18 +02:00
J-Jamet
fd736bd1c2 Keep screen on entry by default 2022-05-30 10:24:47 +02:00
Oymate
ca6a4bfeef Translated using Weblate (Bengali)
Currently translated at 7.2% (44 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn/
2022-05-27 16:16:54 +02:00
Oymate
02e9debc42 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 15.2% (93 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn_BD/
2022-05-27 16:16:53 +02:00
Oymate
9bc9bd8b95 Added translation using Weblate (Bengali) 2022-05-26 15:10:24 +02:00
Park JM
d810f79b7a Translated using Weblate (Korean)
Currently translated at 20.5% (125 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2022-05-24 11:17:53 +02:00
Milo Ivir
f3468951f1 Translated using Weblate (Croatian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-05-22 19:32:57 +02:00
Kunzisoft
7ec5badabb Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-05-20 22:17:36 +02:00
J-Jamet
1ff2f501ca Fix capture database exception 2022-05-19 21:22:13 +02:00
J-Jamet
cfcb49e233 Better management of exceptions 2022-05-19 21:16:29 +02:00
J-Jamet
467df2020e Fix merge 2022-05-19 19:48:43 +02:00
J-Jamet
a961b41de0 Fix file save outside of the app 2022-05-19 19:20:21 +02:00
J-Jamet
40e8d5225e Fix notification and save state 2022-05-19 15:54:34 +02:00
J-Jamet
bc755ae1df Fix progress message 2022-05-19 15:00:12 +02:00
J-Jamet
b1cb0c3786 Fix infinite loop 2022-05-19 13:41:38 +02:00
J-Jamet
090d0fa2db Encapsulate channels 2022-05-19 12:53:12 +02:00
J-Jamet
27918a12b0 Fix small bugs 2022-05-19 11:47:53 +02:00
J-Jamet
ba1498b0b2 Fix error message and better implementation 2022-05-19 11:15:28 +02:00
J-Jamet
cbde96dd82 Add waiting task message and cancellable 2022-05-18 19:49:18 +02:00
J-Jamet
344118a755 Better error management 2022-05-18 18:35:24 +02:00
J-Jamet
259c8a4bd9 Setting to remember hardware key 2022-05-18 16:39:35 +02:00
SHINJI.K
fe92e41e91 Translated using Weblate (Japanese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-05-15 15:18:25 +02:00
zeritti
e58c2f2a99 Translated using Weblate (Czech)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-05-15 15:18:24 +02:00
J-Jamet
f4d5bd1bea Fix save and better write implementation 2022-05-11 15:26:49 +02:00
J-Jamet
20b352cabe Better code encapsulation 2022-05-11 14:19:32 +02:00
J-Jamet
20e35f1a69 Encapsulate database operations 2022-05-11 13:42:48 +02:00
J-Jamet
d963f56d0f Merge branch 'develop' into feature/Hardware_Key 2022-05-11 11:36:20 +02:00
J-Jamet
aecfbc7728 Upgrade gradle 2022-05-11 10:59:14 +02:00
J-Jamet
5734df89f0 Merge branch 'develop' into feature/Hardware_Key 2022-05-11 10:10:52 +02:00
J-Jamet
bdf9b864d4 Merge tag '3.4.4' into develop
3.4.4
2022-05-11 10:00:50 +02:00
J-Jamet
1c0f1a036b Merge branch 'release/3.4.4' 2022-05-11 10:00:42 +02:00
J-Jamet
327c9de464 Change main credential validation 2022-05-10 19:59:56 +02:00
J-Jamet
8b2f994769 Save database with challenge response 2022-05-10 15:02:22 +02:00
J-Jamet
a5e53d872b Open database with challenge response in service 2022-05-09 15:56:53 +02:00
Claudio
1868d90693 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-05-08 16:00:54 +02:00
bondlxv
da0c19e068 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-05-08 16:00:54 +02:00
jazzyjabroni
d1103d8db4 Translated using Weblate (Danish)
Currently translated at 84.8% (517 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2022-05-07 21:13:46 +02:00
solokot
b2e92646a1 Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-05-06 18:36:13 +02:00
J-Jamet
19bc2444bc Merge branch 'develop' into feature/Hardware_Key 2022-05-05 16:15:03 +02:00
J-Jamet
831b649cbb Merge branch 'translations' into develop 2022-05-05 15:59:37 +02:00
Kunzisoft
ded3c204b9 Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-05-05 15:58:56 +02:00
Hosted Weblate
23eec5f066 Merge branch 'origin/develop' into Weblate. 2022-05-05 15:54:24 +02:00
J-Jamet
6c167090e1 Small changes 2022-05-05 15:50:51 +02:00
J-Jamet
7d9eca0d46 Small changes 2022-05-05 15:29:56 +02:00
J-Jamet
c551aff474 Upgrade libs 2022-05-05 15:10:30 +02:00
J-Jamet
e627745358 Prevent Tapjacking #1318 2022-05-05 14:49:36 +02:00
J-Jamet
5a30d9d2b5 * Fix crash in New Android 13 #1321
* Better backstack management for selection mode
2022-05-05 12:45:07 +02:00
Santosh Anantwal
0a46817bbc Translated using Weblate (Marathi)
Currently translated at 1.9% (12 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/mr/
2022-05-04 20:13:11 +02:00
Hosted Weblate
a4134fa8c8 Merge branch 'origin/develop' into Weblate. 2022-05-03 19:23:33 +02:00
Santosh Anantwal
683535a5a6 Translated using Weblate (Marathi)
Currently translated at 1.4% (9 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/mr/
2022-05-03 19:23:33 +02:00
Santosh Anantwal
edb53112c2 Added translation using Weblate (Marathi) 2022-05-03 19:01:31 +02:00
SHINJI.K
83a77af520 Translated using Weblate (Japanese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-05-03 15:13:15 +02:00
SC
df3ae17c7b Translated using Weblate (Portuguese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-05-01 10:10:24 +02:00
abidin toumi
4a1624a443 Translated using Weblate (Arabic)
Currently translated at 78.3% (477 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-05-01 10:10:24 +02:00
wqk317
a8de9f9f9f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-05-01 10:10:23 +02:00
hokonch
3aa5b40acd Translated using Weblate (Japanese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-05-01 10:10:23 +02:00
wqk317
8400f3e874 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (607 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 09:35:42 +02:00
wqk317
b40bca1913 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 09:18:00 +02:00
wqk317
7100257f31 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.1% (604 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 09:17:06 +02:00
SHINJI.K
17df1a4d8a Translated using Weblate (Japanese)
Currently translated at 98.0% (597 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:53:45 +02:00
hokonch
d7a5209c68 Translated using Weblate (Japanese)
Currently translated at 98.0% (597 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:53:45 +02:00
wqk317
076220eacd Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:43:03 +02:00
SHINJI.K
99a50f271a Translated using Weblate (Japanese)
Currently translated at 96.8% (590 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:43:03 +02:00
hokonch
63d265da06 Translated using Weblate (Japanese)
Currently translated at 96.8% (590 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:43:03 +02:00
wqk317
30e3624eb1 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:25:13 +02:00
Eric
88f3713e28 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:25:13 +02:00
wqk317
90f0c22545 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:23:43 +02:00
wqk317
8deed8468d Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:21:23 +02:00
hokonch
923ad26b1b Translated using Weblate (Japanese)
Currently translated at 96.0% (585 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:17:37 +02:00
SHINJI.K
3bc858e4c2 Translated using Weblate (Japanese)
Currently translated at 96.0% (585 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:17:37 +02:00
SHINJI.K
f5a7fa41a7 Translated using Weblate (Japanese)
Currently translated at 95.5% (582 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:10:18 +02:00
hokonch
bf71d5508b Translated using Weblate (Japanese)
Currently translated at 95.5% (582 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:10:17 +02:00
J-Jamet
b44c9cfc51 Opening refactoring 2022-04-28 20:39:26 +02:00
J-Jamet
5b4338abae Better implementation for challenge response intent 2022-04-27 14:39:08 +02:00
John Veness
aa5adc28cb Translated using Weblate (English (United Kingdom))
Currently translated at 3.6% (22 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en_GB/
2022-04-26 20:07:59 +02:00
abidin toumi
2dad013cc0 Translated using Weblate (Arabic)
Currently translated at 75.8% (462 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-04-26 20:07:59 +02:00
solokot
7ade66f3ac Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-26 20:07:58 +02:00
Stephan Paternotte
ed75a64b46 Translated using Weblate (Dutch)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-04-26 20:07:58 +02:00
Kunzisoft
e156b80d91 Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-04-26 20:07:58 +02:00
J-Jamet
e8f79ae467 Fix to open challenge-response dynamically / refactoring methods #8 2022-04-25 21:47:43 +02:00
Kunzisoft
90e4862280 Added translation using Weblate (English (United Kingdom)) 2022-04-25 18:22:17 +02:00
André Marcelo Alvarenga
438080d3d6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-04-23 13:08:01 +02:00
SHINJI.K
3c17605764 Translated using Weblate (Japanese)
Currently translated at 94.2% (574 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-23 13:08:00 +02:00
VfBFan
3f68bc0eda Translated using Weblate (German)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-23 13:08:00 +02:00
J-Jamet
ecbee73eae Add view and first implementation of hardware key #8 2022-04-21 18:03:32 +02:00
J-Jamet
3e4452da00 Fix inherited view after orientation change 2022-04-21 17:43:24 +02:00
Linerly
549c690b56 Translated using Weblate (Indonesian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-04-21 01:11:05 +02:00
Oğuz Ersen
aabe06f29b Translated using Weblate (Turkish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-21 01:11:05 +02:00
Eric
82693c5cd3 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-21 01:11:04 +02:00
Ihor Hordiichuk
37a4f26d2f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-04-21 01:11:04 +02:00
solokot
ca94063c7b Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-21 01:11:04 +02:00
Matthaiks
eadc4bf6c2 Translated using Weblate (Polish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-04-21 01:11:03 +02:00
Retrial
b1c307c86b Translated using Weblate (Greek)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-04-21 01:11:03 +02:00
J-Jamet
1874a0056d Merge branch 'develop' into feature/Hardware_Key 2022-04-20 14:24:10 +02:00
J-Jamet
48331f9552 Merge tag '3.4.3' into develop
3.4.3
2022-04-19 21:17:37 +02:00
J-Jamet
f907aa578a Merge branch 'release/3.4.3' 2022-04-19 21:17:31 +02:00
J-Jamet
41e2620cc1 Generate auto description for github repo 2022-04-19 21:16:55 +02:00
J-Jamet
e7a82b167a fix Fastfile 2022-04-19 18:18:15 +02:00
J-Jamet
088c556b00 Update fastlane to copy the release file with name 2022-04-19 18:04:12 +02:00
J-Jamet
c80343b6d4 Remove unused import 2022-04-19 16:41:13 +02:00
J-Jamet
4e52a8cf60 Show visual title when entry is available in Magikeyboard 2022-04-19 16:10:40 +02:00
J-Jamet
1ed1d4233f Allow to add entry with no info in Magikeyboard 2022-04-19 16:06:16 +02:00
J-Jamet
6e4626bc02 Fix ask lock when database can be saved 2022-04-19 15:47:21 +02:00
J-Jamet
2608ae247f Fix quick search and better loadGroup implementation #1302 2022-04-19 14:35:28 +02:00
J-Jamet
785586bfe9 Remove "Select share info" setting for Magikeyboard #1304 2022-04-19 12:24:51 +02:00
J-Jamet
bdcbb177ae Upgrade to 3.4.3 2022-04-19 11:36:04 +02:00
J-Jamet
15ac365d79 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-04-15 19:29:56 +02:00
Braja Yudhistira
debbcb753b Translated using Weblate (Indonesian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-04-15 18:27:39 +02:00
Linerly
69d73aeaa4 Translated using Weblate (Indonesian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-04-15 18:27:39 +02:00
solokot
dffe53370f Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-15 18:27:37 +02:00
Darin Avdeyeva
4334e6dcdf Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-15 18:27:37 +02:00
Stephan Paternotte
c2c6c093d5 Translated using Weblate (Dutch)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-04-15 18:27:36 +02:00
Cow
77e539eec2 Translated using Weblate (Spanish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-04-15 18:27:36 +02:00
J-Jamet
a57970210e Merge tag '3.4.2' into develop
3.4.2
2022-04-15 13:42:49 +02:00
J-Jamet
1b31a46fb7 Merge branch 'release/3.4.2' 2022-04-15 13:42:43 +02:00
J-Jamet
87f19c74fc Add clean in fastlane 2022-04-15 13:23:25 +02:00
J-Jamet
bd157a9724 Fix small UI color 2022-04-15 13:18:11 +02:00
J-Jamet
5a327eb0db Catch reset app timeout for unexpected exceptions 2022-04-15 13:00:41 +02:00
J-Jamet
4b9c0b0109 Fix navigation bar color in dark mode 2022-04-15 12:54:10 +02:00
J-Jamet
df6b75cdbb Fix color 2022-04-15 12:47:50 +02:00
J-Jamet
0b4f8c122b Fix color 2022-04-15 12:46:37 +02:00
J-Jamet
2a87eaf3e5 Upgrade to 3.4.2 and fix service parameter and workflow 2022-04-15 12:21:14 +02:00
J-Jamet
c52266f5cf Merge branch 'master' of github.com:Kunzisoft/KeePassDX 2022-04-14 19:28:49 +02:00
J-Jamet
3b21f8add2 Merge tag '3.4.1' into develop
3.4.1
2022-04-14 19:28:12 +02:00
J-Jamet
6574bd10a0 Merge branch 'release/3.4.1' 2022-04-14 19:28:05 +02:00
J-Jamet
23f3335988 Update version code for deployment 2022-04-14 19:17:49 +02:00
J-Jamet
a5d7f33c82 Fix unexpected lock of the app #1294 2022-04-14 19:11:33 +02:00
J-Jamet
3782c4dac0 Fix styles 2022-04-14 17:17:38 +02:00
J-Jamet
1fc02fd2fe Small UI changes 2022-04-14 15:16:32 +02:00
J-Jamet
cc347c1dbe Update strings 2022-04-14 15:07:32 +02:00
J-Jamet
79ff20eb18 Remove irrelevant Autofill autosearch setting 2022-04-14 14:55:00 +02:00
J-Jamet
e6e8a447da Clear focus in autosearch and update CHANGELOG 2022-04-14 14:32:34 +02:00
J-Jamet
233f0c5bdb Fix another entry in selection mode with Magikeyboard #1293 2022-04-14 14:12:35 +02:00
J-Jamet
9ed4271a14 Fix search mode with Magikeyboard #1292 2022-04-14 12:50:33 +02:00
J-Jamet
470c0b6b43 Update README description 2022-04-14 11:54:16 +02:00
J-Jamet
afa8ae42b9 Upgrade gradle to 3.4.1 2022-04-14 11:45:55 +02:00
Jérémy JAMET
63d426503f Update FUNDING.yml
Fix issuehunt
2022-04-12 20:43:48 +02:00
Jérémy JAMET
ffb7f80b26 Create FUNDING.yml 2022-04-12 20:42:35 +02:00
J-Jamet
63f8826fd8 Merge branch 'master' into develop 2022-04-12 20:08:21 +02:00
J-Jamet
ef836e8b84 Change screenshots 2022-04-12 20:08:10 +02:00
J-Jamet
abc1c43a51 Merge tag '3.4.0' into develop
3.4.0
2022-04-12 19:45:17 +02:00
J-Jamet
6b54dd9e0d Merge branch 'release/3.4.0' 2022-04-12 19:45:10 +02:00
J-Jamet
1f54e7752d Disable keyboard timeout by default 2022-04-12 15:04:12 +02:00
J-Jamet
6ac941f276 Upgrade to 3.4.0 2022-04-12 14:58:13 +02:00
Hosted Weblate
a4fe92562f Merge branch 'origin/develop' into Weblate. 2022-04-12 12:39:39 +02:00
SC
b9bd1d9d4b Translated using Weblate (Portuguese)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-04-12 12:39:39 +02:00
Milo Ivir
3b6c28488a Translated using Weblate (Croatian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-12 12:39:38 +02:00
Eric
875eb3500d Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-12 12:39:38 +02:00
Ihor Hordiichuk
3a88a2451c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-04-12 12:39:37 +02:00
solokot
6800b73a4f Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-12 12:39:37 +02:00
Matthaiks
983404e6d8 Translated using Weblate (Polish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-04-12 12:39:37 +02:00
Retrial
b95c0a18a7 Translated using Weblate (Greek)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-04-12 12:39:36 +02:00
VfBFan
36b317cad8 Translated using Weblate (German)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 12:39:36 +02:00
Sebastian
35d74888fb Translated using Weblate (Danish)
Currently translated at 84.3% (517 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2022-04-12 12:39:36 +02:00
J-Jamet
6c308483f7 Upgrade gradle and kotlin 2022-04-12 12:31:18 +02:00
J-Jamet
9d25fb74ec Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-04-12 12:14:23 +02:00
J-Jamet
d217b52744 Upgrade to 3.4.0_beta02
Fix #1282 with workaround
2022-04-12 12:12:05 +02:00
J-Jamet
319da4b174 Rollback openOutputStream in "rwt" 2022-04-12 11:32:49 +02:00
nautilusx
9bee467942 Translated using Weblate (German)
Currently translated at 99.6% (611 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 07:46:27 +02:00
VfBFan
44ac70fc97 Translated using Weblate (German)
Currently translated at 99.6% (611 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 07:46:26 +02:00
J-Jamet
d897611d62 Change main screenshot 2022-04-10 17:21:35 +02:00
J-Jamet
c2ae251e73 Change screenshots 2022-04-10 17:10:01 +02:00
J-Jamet
35ad285864 Fix education screen 2022-04-09 16:52:25 +02:00
J-Jamet
97bdae21eb Small change 2022-04-09 16:37:11 +02:00
J-Jamet
d6dc6e43c7 Update strings 2022-04-09 16:35:20 +02:00
Oğuz Ersen
01e6e530d5 Translated using Weblate (Turkish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-09 16:28:35 +02:00
Kunzisoft
9ec0178beb Translated using Weblate (French)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-04-09 16:28:35 +02:00
Hosted Weblate
d66f2f6d24 Merge branch 'origin/develop' into Weblate. 2022-04-09 16:15:08 +02:00
Milo Ivir
79cd4004cc Translated using Weblate (Croatian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-09 16:15:08 +02:00
VfBFan
991243e2df Translated using Weblate (German)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-09 16:15:07 +02:00
J-Jamet
b91cf11d86 Upgrade to 3.4 beta01 2022-04-09 16:14:15 +02:00
J-Jamet
d182ec09fa Upgrade CHANGELOG 2022-04-09 16:12:57 +02:00
J-Jamet
8641822358 Fix small bugs 2022-04-09 16:03:12 +02:00
J-Jamet
9665cbb428 Fix small visual bug 2022-04-08 18:37:06 +02:00
J-Jamet
a280dfaf3b Better magikeyboard views 2022-04-08 18:23:02 +02:00
J-Jamet
3e56521ea8 Empty Magikeyboard memory when the main service is killed #1261 2022-04-08 16:53:03 +02:00
SC
b205230ea9 Translated using Weblate (Portuguese)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-04-08 09:31:01 +02:00
Oğuz Ersen
51645ab126 Translated using Weblate (Turkish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-08 09:31:00 +02:00
Eric
5d04897e75 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-08 09:30:59 +02:00
Ihor Hordiichuk
1ac0ea5cc6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-04-08 09:30:59 +02:00
solokot
a07e8b51e5 Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-08 09:30:58 +02:00
Vitor Henrique
a81f0238f4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-04-08 09:30:57 +02:00
Matthaiks
2b81eb8ec7 Translated using Weblate (Polish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-04-08 09:30:56 +02:00
Retrial
e5eb642781 Translated using Weblate (Greek)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-04-08 09:30:56 +02:00
VfBFan
a4cbe25733 Translated using Weblate (German)
Currently translated at 99.8% (612 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-08 09:30:55 +02:00
J-Jamet
2042c85b22 Ask confirmation to lock if changes without save #970 2022-04-07 18:06:47 +02:00
J-Jamet
3149f8745c Fix passkey view 2022-04-07 17:39:46 +02:00
J-Jamet
15b9f1616f Fix keyboard search and selection 2022-04-07 14:28:45 +02:00
J-Jamet
94c02b7288 Fix selection mode instance issue 2022-04-07 13:26:07 +02:00
J-Jamet
7e70b59a59 Fix selection mode instance issue 2022-04-07 13:22:19 +02:00
Hosted Weblate
2c7f5e41ed Merge branch 'origin/develop' into Weblate. 2022-04-05 18:00:37 +02:00
J-Jamet
108b8df280 Fix CHANGELOG issue number 2022-04-05 15:32:42 +02:00
J-Jamet
553098f9be Manage package name from Magikeyboard #1010 2022-04-05 15:28:15 +02:00
J-Jamet
131eb78407 Setting to change the keyboard during a search #1254 2022-04-05 13:38:36 +02:00
J-Jamet
f956a279a5 Save search parameters #1254 2022-04-05 13:07:36 +02:00
J-Jamet
7150686b92 Fix search parameter parcelable 2022-04-05 11:59:02 +02:00
J-Jamet
4b1fb2c173 Upgrade CHANGELOG 2022-04-05 11:28:58 +02:00
J-Jamet
94464bf608 Save pass generator options in app.properties 2022-04-04 15:30:57 +02:00
J-Jamet
2faa88784a Update CHANGELOG 2022-04-04 15:09:32 +02:00
J-Jamet
e6607b53d8 Better search implementation #175 2022-04-04 15:08:11 +02:00
J-Jamet
3f6a6c864a Show TOTP in 3-digit grouping #1270 2022-04-04 13:25:01 +02:00
J-Jamet
30a578257d Update CHANGELOG 2022-04-04 13:07:38 +02:00
J-Jamet
8411134adf Save files with "wt" #1282 2022-04-04 12:21:35 +02:00
J-Jamet
f86a5d1a19 Fix small bug 2022-04-01 19:19:47 +02:00
J-Jamet
be72492537 Merge branch 'feature/Mnemonics' into develop 2022-04-01 19:14:07 +02:00
J-Jamet
76f9e8ec6e Generate passphrase #218 2022-04-01 19:08:50 +02:00
Milo Ivir
8fb1c44e58 Translated using Weblate (Croatian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-01 13:08:19 +02:00
J-Jamet
f607b35cf3 Change max slider to 64 2022-04-01 11:01:00 +02:00
J-Jamet
0e56bec35a Key generator as tabs 2022-04-01 10:51:38 +02:00
Hosted Weblate
c890d10114 Merge branch 'origin/develop' into Weblate. 2022-03-31 12:09:22 +02:00
Milo Ivir
dee2fe5ce7 Translated using Weblate (Croatian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-03-31 12:09:21 +02:00
J-Jamet
4a4d767bce Manage setting to hide password #696 2022-03-29 17:18:49 +02:00
J-Jamet
d57e0cf601 Add setting to colorize password 2022-03-29 16:41:23 +02:00
J-Jamet
7fa141dd1b Merge branch 'develop' into feature/Mnemonics 2022-03-29 16:01:50 +02:00
J-Jamet
c261a0cbca Smaller tab buttons 2022-03-29 16:01:20 +02:00
J-Jamet
66661cbd49 Add bold and change password colors 2022-03-29 15:54:37 +02:00
J-Jamet
100c126c3d Update CHANGELOG 2022-03-29 15:32:51 +02:00
J-Jamet
d466e3077d Add color for special password chars #454 2022-03-29 15:32:29 +02:00
J-Jamet
24587dc34e Small refactorization 2022-03-29 12:42:53 +02:00
J-Jamet
32cc57dd03 Add editable chars fields #539 2022-03-28 22:14:24 +02:00
J-Jamet
a55488846b Add advanced password filters #1052 2022-03-28 21:26:26 +02:00
J-Jamet
dcf61fd4e2 Update CHANGELOG 2022-03-28 16:34:37 +02:00
J-Jamet
5bf998468a Save password preferences dynamically 2022-03-28 16:24:25 +02:00
J-Jamet
01c9625c59 Dynamic filter change and fix bugs 2022-03-28 15:08:56 +02:00
Dixon Huang
772c378922 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2022-03-28 13:09:14 +02:00
J-Jamet
ee50a91379 Move entropy to the top 2022-03-27 21:40:13 +02:00
J-Jamet
9cfda3bad8 Fix small bugs 2022-03-27 21:33:44 +02:00
J-Jamet
aa19b08bd9 Fix password entropy and add chips 2022-03-27 20:41:51 +02:00
J-Jamet
87f69bb7e2 Entropy #869 2022-03-27 18:56:12 +02:00
J-Jamet
41c0aeedbe Show visual password strength indicator #631 2022-03-27 17:22:52 +02:00
Eric
3cbe53d76f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (595 of 595 strings)

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

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-03-27 00:27:21 +01:00
Stephan Paternotte
be7d35490d Translated using Weblate (Dutch)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-03-27 00:27:20 +01:00
VfBFan
d0ea997c63 Translated using Weblate (German)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-03-27 00:27:20 +01:00
J-Jamet
1fecffeba2 Merge branch 'master' of github.com:Kunzisoft/KeePassDX 2022-03-26 16:28:39 +01:00
J-Jamet
76319a56a2 Merge tag '3.3.3' into develop
3.3.3
2022-03-26 16:27:23 +01:00
J-Jamet
1d71de7031 Merge branch 'release/3.3.3' 2022-03-26 16:27:15 +01:00
Oğuz Ersen
e9a1cfea11 Translated using Weblate (Turkish)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-03-26 04:15:30 +01:00
solokot
9115856d19 Translated using Weblate (Russian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-03-26 04:15:30 +01:00
Matthaiks
8c45266c18 Translated using Weblate (Polish)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-03-26 04:15:29 +01:00
Retrial
6b4130df89 Translated using Weblate (Greek)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-03-26 04:15:29 +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
Jérémy JAMET
7948358d85 Add spaces in issue template 2022-03-18 19:46:32 +01:00
Jérémy JAMET
96b82bb9b2 Remove "please report..." in issue template 2022-03-18 19:45:39 +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
279bd16b74 Best autofill recognition #1250 2022-02-26 13:13:07 +01:00
J-Jamet
2e0081b66c Prepare hardware key in main credential 2022-02-26 12:51:00 +01:00
397 changed files with 11332 additions and 16803 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
#github: [J-Jamet] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: Kunzisoft # Replace with a single Liberapay username
issuehunt: Kunzisoft/KeePassDX # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://www.keepassdx.com/#donation'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -8,9 +8,11 @@ assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
@@ -18,9 +20,11 @@ Steps to reproduce the behavior:
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**KeePass Database**
- Created with: [e.g Windows KeePass 2.42]
- Version: [e.g. 2]
- Location: [e.g. Remote file retrieved with GDrive app]
@@ -28,15 +32,18 @@ A clear and concise description of what you expected to happen.
- Size: [e.g. 150Mo]
- Contains attachment: [e.g. Yes]
**KeePassDX (please complete the following information):**
**KeePassDX:**
- Version: [e.g. 2.5.0.0beta23]
- Build: [e.g. Free]
- Language: [e.g. French]
**Android (please complete the following information):**
**Android:**
- Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1]
**Additional context**
Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

3
.gitignore vendored
View File

@@ -80,6 +80,9 @@ art/screen*.png
art/logo_512.png
art/store_screens/
# Release
releases/*
# Dir linux
.directory
*/.directory

View File

@@ -1,3 +1,61 @@
KeePassDX(3.5.0)
* Support YubiKey challenge-response #8 #137
* Better exception management during database save #1346
* Better management of mime-types and extensions #1211
* Add "Screenshot mode" setting #459 #1377 #1354 (Thx @GianpaMX)
* Hide clipboard sensitive text when copy entry field #1386
KeePassDX(3.4.5)
* Fix custom data in group (fix KeeShare) #1335
* Fix device credential unlocking #1344
* New clipboard manager #1343
* Keep screen on by default when viewing an entry
* Change the order of the search filters
* Fix searchable selection
KeePassDX(3.4.4)
* Fix crash in New Android 13 #1321
* Better backstack management for selection mode
* Prevent Tapjacking #1318
* Small changes #1298
KeePassDX(3.4.3)
* Remove "Select share info" setting for Magikeyboard #1304
* Fix quick search and better loadGroup implementation #1302
* Fix small bugs
KeePassDX(3.4.2)
* Fix service parameter and workflow to remove notification when service is killed
* Fix color
KeePassDX(3.4.1)
* Fix search mode with Magikeyboard #1292
* Fix select another entry with Magikeyboard #1293
* Fix unexpected lock with Magikeyboard #1294
* Small UI changes
KeePassDX(3.4.0)
* Passphrase implementation #218
* Show visual password strength indicator with entropy #631 #869 #454 #1270
* Dynamically save password generator configuration #618 #696
* Add advanced password filters #1052 #448 #983 #271 #539
* Better search implementation #175 #1254 #1267
* Manage package name from Magikeyboard #1010 #1261
* Ask confirmation to lock if changes without save #970
* Fix small bugs #1282
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

10
Gemfile Normal file
View File

@@ -0,0 +1,10 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
source "https://rubygems.org"
gem 'fastlane'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

220
Gemfile.lock Normal file
View File

@@ -0,0 +1,220 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
rexml
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.626.0)
aws-sdk-core (3.140.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.58.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.92.4)
faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.209.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-versioning_android (0.1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.25.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-core (0.7.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.13.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-playcustomapp_v1 (0.10.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-storage_v1 (0.17.0)
google-apis-core (>= 0.7, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.39.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.17.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.2.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.1)
json (2.6.2)
jwt (2.5.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
public_suffix (5.0.0)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
fastlane-plugin-versioning_android
BUNDLED WITH
2.1.4

View File

@@ -1,6 +1,6 @@
# Android KeePassDX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeePassDX is a **multi-format KeePass manager for Android devices**. The app allows creating keys and passwords in a secure way by integrating with the Android design standards.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> **Lightweight password manager for Android**, KeePassDX allows editing encrypted data in a single file in KeePass format and fill in the forms in a secure way.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">

View File

@@ -4,16 +4,16 @@ apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 31
buildToolsVersion "31.0.0"
compileSdkVersion 32
buildToolsVersion "32.0.0"
ndkVersion "21.4.7075529"
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 31
versionCode = 103
versionName = "3.3.1"
targetSdkVersion 32
versionCode = 115
versionName = "3.5.0 Beta01"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -43,30 +43,20 @@ 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_Simple\"," +
@@ -86,7 +76,6 @@ android {
sourceSets {
libre.res.srcDir 'src/libre/res'
pro.res.srcDir 'src/pro/res'
free.res.srcDir 'src/free/res'
}
@@ -104,7 +93,7 @@ android {
}
}
def room_version = "2.4.2"
def room_version = "2.4.3"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@@ -112,14 +101,14 @@ dependencies {
implementation "androidx.appcompat:appcompat:$android_appcompat_version"
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
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.5.0'
implementation 'androidx.media:media:1.6.0'
// Lifecycle - LiveData - ViewModel - Coroutines
implementation "androidx.core:core-ktx:$android_core_version"
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.fragment:fragment-ktx:1.5.2'
implementation "com.google.android.material:material:$android_material_version"
// Token auto complete
// From sources until https://github.com/splitwise/TokenAutoComplete/pull/422 fixed
@@ -138,6 +127,8 @@ dependencies {
// Apache Commons
implementation 'commons-io:commons-io:2.8.0'
implementation 'commons-codec:commons-codec:1.15'
// Password generator
implementation 'me.gosimple:nbvcxz:1.5.0'
// Encrypt lib
implementation project(path: ':crypto')
// Icon pack

View File

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

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">
<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>
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
<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">
<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:translateX="6"
android:translateY="8">
<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:translateX="6"
android:translateY="6">
<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>

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">
<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>
</group>
<group
android:scaleX="0.3939503"
android:scaleY="0.3939503"
android:translateX="33.66343"
android:translateY="233.998">
<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">
<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:translateX="6"
android:translateY="8">
<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:translateX="6"
android:translateY="6">
<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>

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

@@ -89,7 +89,6 @@
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-kdb" />
<data android:mimeType="application/x-kdbx" />
<data android:mimeType="application/x-keepass" />
@@ -131,6 +130,9 @@
<activity
android:name="com.kunzisoft.keepass.activities.IconPickerActivity"
android:configChanges="keyboardHidden" />
<activity
android:name="com.kunzisoft.keepass.activities.KeyGeneratorActivity"
android:configChanges="keyboardHidden" />
<activity
android:name="com.kunzisoft.keepass.activities.ImageViewerActivity"
android:configChanges="keyboardHidden" />
@@ -147,7 +149,8 @@
<activity
android:name="com.kunzisoft.keepass.activities.AutofillLauncherActivity"
android:theme="@style/Theme.Transparent"
android:configChanges="keyboardHidden" />
android:configChanges="keyboardHidden"
android:excludeFromRecents="true"/>
<activity
android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
<activity
@@ -155,6 +158,7 @@
<activity
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
android:theme="@style/Theme.Transparent"
android:launchMode="singleInstance"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@@ -169,9 +173,6 @@
<data android:scheme="otpauth" android:host="hotp" />
</intent-filter>
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity"
android:theme="@style/Theme.Transparent" />
<activity
android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
android:label="@string/keyboard_setting_label"

View File

@@ -30,6 +30,7 @@ import androidx.core.text.HtmlCompat
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.utils.UriUtil
import org.joda.time.DateTime
class AboutActivity : StylishActivity() {
@@ -45,6 +46,12 @@ class AboutActivity : StylishActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
val appName = if (UriUtil.contributingUser(this))
getString(R.string.app_name) + " " + getString(R.string.app_name_part3)
else
getString(R.string.app_name)
findViewById<TextView>(R.id.activity_about_app_name).text = appName
var version: String
var build: String
try {
@@ -70,6 +77,12 @@ class AboutActivity : StylishActivity() {
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
findViewById<TextView>(R.id.activity_about_privacy_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_privacy),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
findViewById<TextView>(R.id.activity_about_contribution_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution),

View File

@@ -44,6 +44,7 @@ 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
@@ -83,6 +84,7 @@ class EntryActivity : DatabaseLockActivity() {
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
@@ -133,6 +135,7 @@ class EntryActivity : DatabaseLockActivity() {
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)
@@ -162,6 +165,19 @@ class EntryActivity : DatabaseLockActivity() {
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 ->
@@ -193,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

View File

@@ -58,9 +58,13 @@ import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.template.*
import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
@@ -78,11 +82,9 @@ import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import org.joda.time.DateTime
import java.util.*
import kotlin.collections.ArrayList
class EntryEditActivity : DatabaseLockActivity(),
EntryCustomFieldDialogFragment.EntryCustomFieldListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener,
DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener,
@@ -119,6 +121,20 @@ class EntryEditActivity : DatabaseLockActivity(),
mEntryEditViewModel.selectIcon(icon)
}
private var mPasswordField: Field? = null
private var mKeyGeneratorResultLauncher = KeyGeneratorActivity.registerForGeneratedKeyResult(this) { keyGenerated ->
keyGenerated?.let {
mPasswordField?.let {
it.protectedValue.stringValue = keyGenerated
mEntryEditViewModel.selectPassword(it)
}
}
mPasswordField = null
Handler(Looper.getMainLooper()).post {
performedNextEducation()
}
}
// To ask data lost only one time
private var backPressedAlreadyApproved = false
@@ -268,9 +284,8 @@ class EntryEditActivity : DatabaseLockActivity(),
}
mEntryEditViewModel.requestPasswordSelection.observe(this) { passwordField ->
GeneratePasswordDialogFragment
.getInstance(passwordField)
.show(supportFragmentManager, "PasswordGeneratorFragment")
mPasswordField = passwordField
KeyGeneratorActivity.launch(this, mKeyGeneratorResultLauncher)
}
mEntryEditViewModel.requestCustomFieldEdition.observe(this) { field ->
@@ -420,9 +435,10 @@ class EntryEditActivity : DatabaseLockActivity(),
private fun entryValidatedForKeyboardSelection(database: Database, entry: Entry) {
// Populate Magikeyboard with entry
populateKeyboardAndMoveAppToBackground(this,
entry.getEntryInfo(database),
intent)
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
this,
entry.getEntryInfo(database)
)
onValidateSpecialMode()
// Don't keep activity history for entry edition
finishForEntryResult(entry)
@@ -463,6 +479,11 @@ class EntryEditActivity : DatabaseLockActivity(),
}
}
}
// Keep the screen on
if (PreferencesUtil.isKeepScreenOnEnabled(this)) {
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
override fun onPause() {
@@ -656,17 +677,6 @@ class EntryEditActivity : DatabaseLockActivity(),
mEntryEditViewModel.selectTime(hours, minutes)
}
override fun acceptPassword(passwordField: Field) {
mEntryEditViewModel.selectPassword(passwordField)
Handler(Looper.getMainLooper()).post {
performedNextEducation()
}
}
override fun cancelPassword(passwordField: Field) {
// Do nothing here
}
override fun onBackPressed() {
onApprovedBackPressed {
super@EntryEditActivity.onBackPressed()

View File

@@ -19,20 +19,18 @@
*/
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil
/**
* Activity to search or select entry in database,
@@ -45,36 +43,61 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
return false
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
var sharedWebDomain: String? = null
var otpString: String? = null
when (intent?.action) {
Intent.ACTION_SEND -> {
if ("text/plain" == intent.type) {
// Retrieve web domain or OTP
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra ->
val keySelectionBundle = intent.getBundleExtra(KEY_SELECTION_BUNDLE)
if (keySelectionBundle != null) {
// To manage package name
var searchInfo = SearchInfo()
keySelectionBundle.getParcelable<SearchInfo>(KEY_SEARCH_INFO)?.let { mSearchInfo ->
searchInfo = mSearchInfo
}
launch(database, searchInfo)
} else {
// To manage share
var sharedWebDomain: String? = null
var otpString: String? = null
when (intent?.action) {
Intent.ACTION_SEND -> {
if ("text/plain" == intent.type) {
// Retrieve web domain or OTP
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra ->
if (OtpEntryFields.isOTPUri(extra))
otpString = extra
else
sharedWebDomain = Uri.parse(extra).host
}
}
launchSelection(database, sharedWebDomain, otpString)
}
Intent.ACTION_VIEW -> {
// Retrieve OTP
intent.dataString?.let { extra ->
if (OtpEntryFields.isOTPUri(extra))
otpString = extra
else
sharedWebDomain = Uri.parse(extra).host
}
launchSelection(database, sharedWebDomain, otpString)
}
else -> {
if (database != null) {
GroupActivity.launch(this, database)
} else {
FileDatabaseSelectActivity.launch(this)
}
}
}
Intent.ACTION_VIEW -> {
// Retrieve OTP
intent.dataString?.let { extra ->
if (OtpEntryFields.isOTPUri(extra))
otpString = extra
}
}
else -> {}
}
finish()
}
private fun launchSelection(database: Database?,
sharedWebDomain: String?,
otpString: String?) {
// Build domain search param
val searchInfo = SearchInfo().apply {
this.webDomain = sharedWebDomain
@@ -90,111 +113,111 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
private fun launch(database: Database?,
searchInfo: SearchInfo) {
if (!searchInfo.containsOnlyNullValues()) {
// Setting to integrate Magikeyboard
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
// Setting to integrate Magikeyboard
val searchShareForMagikeyboard = MagikeyboardService.activatedInSettings(this)
// If database is open
val readOnly = database?.isReadOnly != false
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ openedDatabase, items ->
// Items found
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(
this,
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (searchShareForMagikeyboard) {
if (items.size == 1) {
// Automatically populate keyboard
val entryPopulate = items[0]
populateKeyboardAndMoveAppToBackground(
this,
entryPopulate,
intent)
} else {
// Select the one we want
GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase,
searchInfo,
true)
}
} else {
GroupActivity.launchForSearchResult(this,
openedDatabase,
searchInfo,
true)
}
},
{ openedDatabase ->
// Show the database UI to select the entry
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(this,
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (readOnly || searchShareForMagikeyboard) {
GroupActivity.launchForKeyboardSelectionResult(this,
// If database is open
val readOnly = database?.isReadOnly != false
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ openedDatabase, items ->
// Items found
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(
this,
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (searchShareForMagikeyboard) {
MagikeyboardService.performSelection(
items,
{ entryInfo ->
// Automatically populate keyboard
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
this,
entryInfo
)
},
{ autoSearch ->
GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase,
searchInfo,
autoSearch)
}
)
} else {
GroupActivity.launchForSearchResult(this,
openedDatabase,
searchInfo,
true)
}
},
{ openedDatabase ->
// Show the database UI to select the entry
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(this,
openedDatabase,
searchInfo,
false)
}
},
{
// 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)
} else {
FileDatabaseSelectActivity.launchForSearchResult(this,
searchInfo)
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (searchShareForMagikeyboard) {
GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase,
searchInfo,
false)
} else {
GroupActivity.launchForSearchResult(this,
openedDatabase,
searchInfo,
false)
}
)
},
{
// If database not open
if (searchInfo.otpString != null) {
FileDatabaseSelectActivity.launchForSaveResult(this,
searchInfo)
} else if (searchShareForMagikeyboard) {
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
searchInfo)
} else {
FileDatabaseSelectActivity.launchForSearchResult(this,
searchInfo)
}
}
)
}
companion object {
private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE"
private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
fun launch(context: Context,
searchInfo: SearchInfo? = null) {
val intent = Intent(context, EntrySelectionLauncherActivity::class.java).apply {
putExtra(KEY_SELECTION_BUNDLE, Bundle().apply {
putParcelable(KEY_SEARCH_INFO, searchInfo)
})
}
// New task needed because don't launch from an Activity context
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
}
finish()
}
}
fun populateKeyboardAndMoveAppToBackground(activity: Activity,
entry: EntryInfo,
intent: Intent,
toast: Boolean = true) {
// Populate Magikeyboard with entry
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
// Consume the selection mode
EntrySelectionHelper.removeModesFromIntent(intent)
activity.moveTaskToBack(true)
}

View File

@@ -37,6 +37,7 @@ 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
@@ -54,7 +55,8 @@ import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -73,6 +75,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
// Views
private lateinit var coordinatorLayout: CoordinatorLayout
private var specialTitle: View? = null
private var createDatabaseButtonView: View? = null
private var openDatabaseButtonView: View? = null
@@ -112,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() }
@@ -150,8 +156,9 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
fileDatabaseHistoryEntityToOpen.databaseUri?.let { databaseFileUri ->
launchPasswordActivity(
databaseFileUri,
fileDatabaseHistoryEntityToOpen.keyFileUri
databaseFileUri,
fileDatabaseHistoryEntityToOpen.keyFileUri,
fileDatabaseHistoryEntityToOpen.hardwareKey
)
}
}
@@ -245,7 +252,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
?: MainCredential()
databaseFilesViewModel.addDatabaseFile(
databaseUri,
mainCredential.keyFileUri
mainCredential.keyFileUri,
mainCredential.hardwareKey
)
}
}
@@ -292,10 +300,11 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_LONG).asError().show()
}
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?, hardwareKey: HardwareKey?) {
MainCredentialActivity.launch(this,
databaseUri,
keyFile,
hardwareKey,
{ exception ->
fileNoFoundAction(exception)
},
@@ -315,18 +324,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
}
}
override fun onValidateSpecialMode() {
super.onValidateSpecialMode()
finish()
}
override fun onCancelSpecialMode() {
super.onCancelSpecialMode()
finish()
}
private fun launchPasswordActivityWithPath(databaseUri: Uri) {
launchPasswordActivity(databaseUri, null)
launchPasswordActivity(databaseUri, null, null)
// Delete flickering for kitkat <=
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
overridePendingTransition(0, 0)
@@ -335,6 +334,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 -> {
@@ -391,7 +393,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
super.onCreateOptionsMenu(menu)
if (mSpecialMode == SpecialMode.DEFAULT) {
MenuUtil.defaultMenuInflater(menuInflater, menu)
MenuUtil.defaultMenuInflater(this, menuInflater, menu)
}
Handler(Looper.getMainLooper()).post {

View File

@@ -67,8 +67,9 @@ import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
@@ -79,6 +80,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
@@ -122,8 +124,6 @@ class GroupActivity : DatabaseLockActivity(),
private var mBreadcrumbAdapter: BreadcrumbAdapter? = null
private var mSearchMenuItem: MenuItem? = null
private var mGroupFragment: GroupFragment? = null
private var mRecyclingBinEnabled = false
private var mRecyclingBinIsCurrentGroup = false
@@ -182,6 +182,11 @@ class GroupActivity : DatabaseLockActivity(),
addSearch()
//loadGroup()
// Back to previous keyboard
if (PreferencesUtil.isKeyboardPreviousSearchEnable(this@GroupActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
return true
}
@@ -200,7 +205,7 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction()
if (mSearchState == null) {
mSearchState = SearchState(searchFiltersView?.searchParameters
?: SearchParameters(), 0)
?: PreferencesUtil.getDefaultSearchParameters(this), 0)
}
}
@@ -402,20 +407,14 @@ class GroupActivity : DatabaseLockActivity(),
val currentGroup = it.group
mCurrentGroup = currentGroup
if (currentGroup.isVirtual) {
val searchParameters = it.searchParameters
mSearchState = SearchState(searchParameters, it.showFromPosition)
mSearchState = SearchState(
it.searchParameters,
it.showFromPosition
)
}
// Main and search groups in activity are managed with another variables
// to keep values during orientation
// Expand the search view if defined in settings
if (mRequestStartupSearch
&& PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
// To request search only one time
mRequestStartupSearch = false
mSearchMenuItem?.expandActionView()
}
loadingView?.hideByFading()
}
@@ -719,11 +718,16 @@ class GroupActivity : DatabaseLockActivity(),
val stringQuery = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
intent.action = Intent.ACTION_DEFAULT
intent.removeExtra(SearchManager.QUERY)
mSearchState = SearchState(SearchParameters().apply {
mSearchState = SearchState(PreferencesUtil.getDefaultSearchParameters(this).apply {
searchQuery = stringQuery
}, mSearchState?.firstVisibleItem ?: 0)
} else if (mRequestStartupSearch
&& PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
// Expand the search view if defined in settings
// To request search only one time
mRequestStartupSearch = false
addSearch()
}
loadGroup()
}
}
@@ -884,10 +888,9 @@ class GroupActivity : DatabaseLockActivity(),
private fun entrySelectedForKeyboardSelection(database: Database, entry: Entry) {
reloadCurrentGroup()
// Populate Magikeyboard with entry
populateKeyboardAndMoveAppToBackground(
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
this,
entry.getEntryInfo(database),
intent
entry.getEntryInfo(database)
)
onValidateSpecialMode()
}
@@ -1126,6 +1129,7 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction()
searchView?.setOnQueryTextListener(null)
searchFiltersView?.saveSearchParameters()
}
private fun addSearchQueryInSearchView(searchQuery: String) {
@@ -1173,7 +1177,6 @@ class GroupActivity : DatabaseLockActivity(),
// Get the SearchView and set the searchable configuration
menu.findItem(R.id.menu_search)?.let {
mLockSearchListeners = true
mSearchMenuItem = it
it.setOnActionExpandListener(mOnSearchActionExpandListener)
searchView = it.actionView as SearchView?
searchView?.apply {
@@ -1605,50 +1608,31 @@ class GroupActivity : DatabaseLockActivity(),
autofillActivityResultLauncher: ActivityResultLauncher<Intent>?) {
EntrySelectionHelper.doSpecialAction(activity.intent,
{
GroupActivity.launch(
// Default action
launch(
activity,
database,
true
)
},
{ searchInfo ->
SearchHelper.checkAutoSearchInfo(activity,
// Search action
if (database.loaded) {
launchForSearchResult(activity,
database,
searchInfo,
{ _, _ ->
// Response is build
GroupActivity.launchForSearchResult(activity,
database,
searchInfo,
true)
onLaunchActivitySpecialMode()
},
{
// Here no search info found
if (database.isReadOnly) {
GroupActivity.launchForSearchResult(activity,
database,
searchInfo,
false)
} else {
GroupActivity.launchForSaveResult(activity,
database,
searchInfo,
false)
}
onLaunchActivitySpecialMode()
},
{
// Simply close if database not opened, normally not happened
onCancelSpecialMode()
}
)
true)
onLaunchActivitySpecialMode()
} else {
// Simply close if database not opened
onCancelSpecialMode()
}
},
{ searchInfo ->
// Save info used with OTP
// Save info
if (database.loaded) {
if (!database.isReadOnly) {
GroupActivity.launchForSaveResult(
launchForSaveResult(
activity,
database,
searchInfo,
@@ -1667,28 +1651,33 @@ class GroupActivity : DatabaseLockActivity(),
}
},
{ searchInfo ->
// Keyboard selection
SearchHelper.checkAutoSearchInfo(activity,
database,
searchInfo,
{ _, items ->
// Response is build
if (items.size == 1) {
populateKeyboardAndMoveAppToBackground(activity,
items[0],
activity.intent)
onValidateSpecialMode()
} else {
// Select the one we want
GroupActivity.launchForKeyboardSelectionResult(activity,
database,
searchInfo,
true)
onLaunchActivitySpecialMode()
}
MagikeyboardService.performSelection(
items,
{ entryInfo ->
// Keyboard populated
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
activity,
entryInfo
)
onValidateSpecialMode()
},
{ autoSearch ->
launchForKeyboardSelectionResult(activity,
database,
searchInfo,
autoSearch)
onLaunchActivitySpecialMode()
}
)
},
{
// Here no search info found, disable auto search
GroupActivity.launchForKeyboardSelectionResult(activity,
launchForKeyboardSelectionResult(activity,
database,
searchInfo,
false)
@@ -1701,6 +1690,7 @@ class GroupActivity : DatabaseLockActivity(),
)
},
{ searchInfo, autofillComponent ->
// Autofill selection
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SearchHelper.checkAutoSearchInfo(activity,
database,
@@ -1712,7 +1702,7 @@ class GroupActivity : DatabaseLockActivity(),
},
{
// Here no search info found, disable auto search
GroupActivity.launchForAutofillResult(activity,
launchForAutofillResult(activity,
database,
autofillActivityResultLauncher,
autofillComponent,
@@ -1730,20 +1720,21 @@ class GroupActivity : DatabaseLockActivity(),
}
},
{ registerInfo ->
// Autofill registration
if (!database.isReadOnly) {
SearchHelper.checkAutoSearchInfo(activity,
database,
registerInfo?.searchInfo,
{ _, _ ->
// No auto search, it's a registration
GroupActivity.launchForRegistration(activity,
launchForRegistration(activity,
database,
registerInfo)
onLaunchActivitySpecialMode()
},
{
// Here no search info found, disable auto search
GroupActivity.launchForRegistration(activity,
launchForRegistration(activity,
database,
registerInfo)
onLaunchActivitySpecialMode()

View File

@@ -0,0 +1,139 @@
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.commit
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.KeyGeneratorFragment
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.updateLockPaddingLeft
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class KeyGeneratorActivity : DatabaseLockActivity() {
private lateinit var toolbar: Toolbar
private lateinit var coordinatorLayout: CoordinatorLayout
private lateinit var validationButton: View
private var lockView: View? = null
private val keyGeneratorViewModel: KeyGeneratorViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_key_generator)
toolbar = findViewById(R.id.toolbar)
toolbar.title = " "
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
coordinatorLayout = findViewById(R.id.key_generator_coordinator)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
validationButton = findViewById(R.id.key_generator_validation)
validationButton.setOnClickListener {
keyGeneratorViewModel.validateKeyGenerated()
}
supportFragmentManager.commit {
replace(R.id.key_generator_fragment, KeyGeneratorFragment.getInstance(
// Default selection tab
KeyGeneratorFragment.KeyGeneratorTab.PASSWORD
), KEY_GENERATED_FRAGMENT_TAG
)
}
keyGeneratorViewModel.keyGenerated.observe(this) { keyGenerated ->
setResult(Activity.RESULT_OK, Intent().apply {
putExtra(KEY_GENERATED, keyGenerated)
})
finish()
}
}
override fun viewToInvalidateTimeout(): View? {
return findViewById<ViewGroup>(R.id.key_generator_container)
}
override fun onResume() {
super.onResume()
// Show the lock button
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
// Padding if lock button visible
toolbar.updateLockPaddingLeft()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.key_generator, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
}
R.id.menu_generate -> {
keyGeneratorViewModel.requireKeyGeneration()
}
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED, Intent())
super.onBackPressed()
}
companion object {
private const val KEY_GENERATED = "KEY_GENERATED"
private const val KEY_GENERATED_FRAGMENT_TAG = "KEY_GENERATED_FRAGMENT_TAG"
fun registerForGeneratedKeyResult(activity: FragmentActivity,
keyGeneratedListener: (String?) -> Unit): ActivityResultLauncher<Intent> {
return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
keyGeneratedListener.invoke(
result.data?.getStringExtra(KEY_GENERATED)
)
} else {
keyGeneratedListener.invoke(null)
}
}
}
fun launch(context: FragmentActivity,
resultLauncher: ActivityResultLauncher<Intent>) {
// Create an instance to return the picker icon
resultLauncher.launch(
Intent(context, KeyGeneratorActivity::class.java)
)
}
}
}

View File

@@ -1,59 +0,0 @@
/*
* 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.activities
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
/**
* Activity to select entry in database and populate it in Magikeyboard
*/
class MagikeyboardLauncherActivity : DatabaseModeActivity() {
override fun applyCustomStyle(): Boolean {
return false
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
SearchHelper.checkAutoSearchInfo(this,
database,
null,
{ _, _ ->
// Not called
// if items found directly returns before calling this activity
},
{ openedDatabase ->
// Select if not found
GroupActivity.launchForKeyboardSelectionResult(this, openedDatabase)
},
{
// Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
}
)
finish()
}
}

View File

@@ -34,11 +34,13 @@ 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
@@ -52,10 +54,13 @@ import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
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.element.MainCredential
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_DATABASE_KEY
@@ -63,6 +68,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
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
@@ -79,6 +85,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
// Views
private var toolbar: Toolbar? = null
private var filenameView: TextView? = null
private var advancedUnlockButton: View? = null
private var mainCredentialView: MainCredentialView? = null
private var confirmButtonView: Button? = null
private var infoContainerView: ViewGroup? = null
@@ -96,6 +103,8 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
private var mRememberKeyFile: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null
private var mRememberHardwareKey: Boolean = false
private var mReadOnly: Boolean = false
private var mForceReadOnly: Boolean = false
@@ -116,6 +125,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
supportActionBar?.setDisplayShowHomeEnabled(true)
filenameView = findViewById(R.id.filename)
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)
@@ -127,11 +137,13 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
PreferencesUtil.enableReadOnlyDatabase(this)
}
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mRememberHardwareKey = PreferencesUtil.rememberHardwareKey(this)
mExternalFileHelper = ExternalFileHelper(this@MainCredentialActivity)
// Build elements to manage keyfile selection
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) {
mainCredentialView?.populateKeyFileTextView(uri)
mainCredentialView?.populateKeyFileView(uri)
}
}
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
@@ -143,6 +155,11 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
getUriFromIntent(intent)
// 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) {
@@ -160,6 +177,16 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
mainCredentialView?.onKeyFileChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
mainCredentialView?.onHardwareKeyChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
// Observe if default database
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
@@ -193,10 +220,19 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
databaseKeyFileUri
}
val databaseHardwareKey = mainCredentialView?.getMainCredential()?.hardwareKey
val hardwareKey =
if (mRememberHardwareKey
&& databaseHardwareKey == null) {
databaseFile?.hardwareKey
} else {
databaseHardwareKey
}
// Define title
filenameView?.text = databaseFile?.databaseAlias ?: ""
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri)
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri, hardwareKey)
}
}
@@ -204,6 +240,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
super.onResume()
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@MainCredentialActivity)
mRememberHardwareKey = PreferencesUtil.rememberHardwareKey(this@MainCredentialActivity)
// Back to previous keyboard is setting activated
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@MainCredentialActivity)) {
@@ -227,6 +264,15 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
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)
}
}
@@ -312,24 +358,36 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
private fun getUriFromIntent(intent: Intent?) {
// If is a view intent
val action = intent?.action
if (action != null
&& action == VIEW_INTENT) {
mDatabaseFileUri = intent.data
mainCredentialView?.populateKeyFileTextView(UriUtil.getUriFromIntent(intent, KEY_KEYFILE))
if (action == VIEW_INTENT) {
fillCredentials(
intent.data,
UriUtil.getUriFromIntent(intent, KEY_KEYFILE),
HardwareKey.getHardwareKeyFromString(intent.getStringExtra(KEY_HARDWARE_KEY))
)
} else {
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
intent?.getParcelableExtra<Uri?>(KEY_KEYFILE)?.let {
mainCredentialView?.populateKeyFileTextView(it)
}
fillCredentials(
intent?.getParcelableExtra(KEY_FILENAME),
intent?.getParcelableExtra(KEY_KEYFILE),
HardwareKey.getHardwareKeyFromString(intent?.getStringExtra(KEY_HARDWARE_KEY))
)
}
try {
intent?.removeExtra(KEY_KEYFILE)
intent?.removeExtra(KEY_HARDWARE_KEY)
} catch (e: Exception) {}
mDatabaseFileUri?.let {
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
}
}
private fun fillCredentials(databaseUri: Uri?,
keyFileUri: Uri?,
hardwareKey: HardwareKey?) {
mDatabaseFileUri = databaseUri
mainCredentialView?.populateKeyFileView(keyFileUri)
mainCredentialView?.populateHardwareKeyView(hardwareKey)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
getUriFromIntent(intent)
@@ -338,7 +396,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
private fun launchGroupActivityIfLoaded(database: Database) {
// Check if database really loaded
if (database.loaded) {
clearCredentialsViews(true)
clearCredentialsViews(clearKeyFile = true, clearHardwareKey = true)
GroupActivity.launch(this,
database,
{ onValidateSpecialMode() },
@@ -349,16 +407,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
}
}
override fun onValidateSpecialMode() {
super.onValidateSpecialMode()
finish()
}
override fun onCancelSpecialMode() {
super.onCancelSpecialMode()
finish()
}
override fun retrieveCredentialForEncryption(): ByteArray {
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
?: byteArrayOf()
@@ -398,7 +446,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()
when (cipherDecryptDatabase.credentialStorage) {
CredentialStorage.PASSWORD -> {
mainCredential.masterPassword = String(cipherDecryptDatabase.decryptedValue)
mainCredential.password = String(cipherDecryptDatabase.decryptedValue)
}
CredentialStorage.KEY_FILE -> {
// TODO advanced unlock key file
@@ -413,14 +461,23 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
)
}
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) {
private fun onDatabaseFileLoaded(databaseFileUri: Uri?,
keyFileUri: Uri?,
hardwareKey: HardwareKey?) {
// Define Key File text
if (mRememberKeyFile) {
mainCredentialView?.populateKeyFileTextView(keyFileUri)
mainCredentialView?.populateKeyFileView(keyFileUri)
}
// Define hardware key
if (mRememberHardwareKey) {
mainCredentialView?.populateHardwareKeyView(hardwareKey)
}
// Define listener for validate button
confirmButtonView?.setOnClickListener { loadDatabase() }
confirmButtonView?.setOnClickListener {
mainCredentialView?.validateCredential()
}
// If Activity is launch with a password and want to open directly
val intent = intent
@@ -452,10 +509,14 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
}
}
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile,
clearHardwareKey: Boolean = !mRememberHardwareKey) {
mainCredentialView?.populatePasswordTextView(null)
if (clearKeyFile) {
mainCredentialView?.populateKeyFileTextView(null)
mainCredentialView?.populateKeyFileView(null)
}
if (clearHardwareKey) {
mainCredentialView?.populateHardwareKeyView(null)
}
}
@@ -539,7 +600,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
}
if (mSpecialMode == SpecialMode.DEFAULT) {
MenuUtil.defaultMenuInflater(inflater, menu)
MenuUtil.defaultMenuInflater(this, inflater, menu)
}
super.onCreateOptionsMenu(menu)
@@ -588,15 +649,29 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
{
performedNextEducation(menu)
})
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(this)
if ((biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockButton != null) {
mPasswordActivityEducation.checkAndPerformedBiometricEducation(
advancedUnlockButton!!,
{
startActivity(
Intent(
this,
SettingsAdvancedUnlockActivity::class.java
)
)
},
{
advancedUnlockFragment?.performEducation(mPasswordActivityEducation,
readOnlyEducationPerformed,
{
performedNextEducation(menu)
},
{
performedNextEducation(menu)
})
})
}
}
} catch (ignored: Exception) {}
}
}
@@ -632,18 +707,24 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile"
private const val KEY_HARDWARE_KEY = "hardwareKey"
private const val VIEW_INTENT = "android.intent.action.VIEW"
private const val KEY_READ_ONLY = "KEY_READ_ONLY"
private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
private fun buildAndLaunchIntent(activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, MainCredentialActivity::class.java)
intent.putExtra(KEY_FILENAME, databaseFile)
if (keyFile != null)
intent.putExtra(KEY_KEYFILE, keyFile)
if (hardwareKey != null)
intent.putExtra(KEY_HARDWARE_KEY, hardwareKey.toString())
intentBuildLauncher.invoke(intent)
}
@@ -656,8 +737,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
@Throws(FileNotFoundException::class)
fun launch(activity: Activity,
databaseFile: Uri,
keyFile: Uri?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
keyFile: Uri?,
hardwareKey: HardwareKey?) {
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
activity.startActivity(intent)
}
}
@@ -672,8 +754,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForSearchResult(activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
searchInfo: SearchInfo) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForSearchModeResult(
activity,
intent,
@@ -691,8 +774,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForSaveResult(activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
searchInfo: SearchInfo) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForSaveModeResult(
activity,
intent,
@@ -710,8 +794,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForKeyboardResult(activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForKeyboardSelectionModeResult(
activity,
intent,
@@ -730,10 +815,11 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForAutofillResult(activity: AppCompatActivity,
databaseFile: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
activityResultLauncher: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
@@ -751,8 +837,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForRegistration(activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
registerInfo: RegisterInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForRegistrationModeResult(
activity,
intent,
@@ -768,6 +855,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launch(activity: AppCompatActivity,
databaseUri: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
fileNoFoundAction: (exception: FileNotFoundException) -> Unit,
onCancelSpecialMode: () -> Unit,
onLaunchActivitySpecialMode: () -> Unit,
@@ -776,43 +864,67 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
try {
EntrySelectionHelper.doSpecialAction(activity.intent,
{
MainCredentialActivity.launch(activity,
databaseUri, keyFile)
launch(
activity,
databaseUri,
keyFile,
hardwareKey
)
},
{ searchInfo -> // Search Action
MainCredentialActivity.launchForSearchResult(activity,
databaseUri, keyFile,
searchInfo)
launchForSearchResult(
activity,
databaseUri,
keyFile,
hardwareKey,
searchInfo
)
onLaunchActivitySpecialMode()
},
{ searchInfo -> // Save Action
MainCredentialActivity.launchForSaveResult(activity,
databaseUri, keyFile,
searchInfo)
launchForSaveResult(
activity,
databaseUri,
keyFile,
hardwareKey,
searchInfo
)
onLaunchActivitySpecialMode()
},
{ searchInfo -> // Keyboard Selection Action
MainCredentialActivity.launchForKeyboardResult(activity,
databaseUri, keyFile,
searchInfo)
launchForKeyboardResult(
activity,
databaseUri,
keyFile,
hardwareKey,
searchInfo
)
onLaunchActivitySpecialMode()
},
{ searchInfo, autofillComponent -> // Autofill Selection Action
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
MainCredentialActivity.launchForAutofillResult(activity,
databaseUri, keyFile,
autofillActivityResultLauncher,
autofillComponent,
searchInfo)
launchForAutofillResult(
activity,
databaseUri,
keyFile,
hardwareKey,
autofillActivityResultLauncher,
autofillComponent,
searchInfo
)
onLaunchActivitySpecialMode()
} else {
onCancelSpecialMode()
}
},
{ registerInfo -> // Registration Action
MainCredentialActivity.launchForRegistration(activity,
databaseUri, keyFile,
registerInfo)
launchForRegistration(
activity,
databaseUri,
keyFile,
hardwareKey,
registerInfo
)
onLaunchActivitySpecialMode()
}
)

View File

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

View File

@@ -27,7 +27,7 @@ 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.database.element.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.MainCredentialView
@@ -95,7 +95,7 @@ class MainCredentialDialogFragment : DatabaseDialogFragment() {
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) {
mainCredentialView?.populateKeyFileTextView(uri)
mainCredentialView?.populateKeyFileView(uri)
}
}
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)

View File

@@ -26,7 +26,7 @@ import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.database.element.MainCredential
class PasswordEncodingDialogFragment : DialogFragment() {

View File

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

View File

@@ -35,42 +35,53 @@ import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.password.PasswordEntropy
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.HardwareKeySelectionView
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.PassKeyView
import com.kunzisoft.keepass.view.applyFontVisibility
class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
private var mMasterPassword: String? = null
private var mKeyFile: Uri? = null
private var mKeyFileUri: Uri? = null
private var mHardwareKey: HardwareKey? = null
private var rootView: View? = null
private lateinit var rootView: View
private var passwordCheckBox: CompoundButton? = null
private lateinit var passwordCheckBox: CompoundButton
private lateinit var passwordView: PassKeyView
private lateinit var passwordRepeatTextInputLayout: TextInputLayout
private lateinit var passwordRepeatView: TextView
private var passwordTextInputLayout: TextInputLayout? = null
private var passwordView: TextView? = null
private var passwordRepeatTextInputLayout: TextInputLayout? = null
private var passwordRepeatView: TextView? = null
private lateinit var keyFileCheckBox: CompoundButton
private lateinit var keyFileSelectionView: KeyFileSelectionView
private var keyFileCheckBox: CompoundButton? = null
private var keyFileSelectionView: KeyFileSelectionView? = null
private lateinit var hardwareKeyCheckBox: CompoundButton
private lateinit var hardwareKeySelectionView: HardwareKeySelectionView
private var mListener: AssignMainCredentialDialogListener? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var mPasswordEntropyCalculator: PasswordEntropy? = null
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
private var mNoKeyConfirmationDialog: AlertDialog? = null
private var mEmptyKeyFileConfirmationDialog: AlertDialog? = null
private var mAllowNoMasterKey: Boolean = false
private val passwordTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
passwordCheckBox?.isChecked = true
passwordCheckBox.isChecked = true
}
}
@@ -100,13 +111,19 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
super.onDetach()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create the password entropy object
mPasswordEntropyCalculator = PasswordEntropy()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
var allowNoMasterKey = false
arguments?.apply {
if (containsKey(ALLOW_NO_MASTER_KEY_ARG))
allowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
mAllowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
}
val builder = AlertDialog.Builder(activity)
@@ -118,63 +135,63 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
rootView?.findViewById<View>(R.id.credentials_information)?.setOnClickListener {
rootView.findViewById<View>(R.id.credentials_information)?.setOnClickListener {
UriUtil.gotoUrl(activity, R.string.credentials_explanation_url)
}
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
passwordView = rootView?.findViewById(R.id.pass_password)
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
passwordCheckBox = rootView.findViewById(R.id.password_checkbox)
passwordView = rootView.findViewById(R.id.password_view)
passwordRepeatTextInputLayout = rootView.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView.findViewById(R.id.password_confirmation)
passwordRepeatView.applyFontVisibility()
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
keyFileCheckBox = rootView.findViewById(R.id.keyfile_checkbox)
keyFileSelectionView = rootView.findViewById(R.id.keyfile_selection)
hardwareKeyCheckBox = rootView.findViewById(R.id.hardware_key_checkbox)
hardwareKeySelectionView = rootView.findViewById(R.id.hardware_key_selection)
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null
keyFileCheckBox?.isChecked = true
keyFileSelectionView?.uri = pathUri
keyFileSelectionView.error = null
keyFileCheckBox.isChecked = true
keyFileSelectionView.uri = pathUri
if (lengthFile <= 0L) {
showEmptyKeyFileConfirmationDialog()
}
}
}
}
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
keyFileSelectionView.setOpenDocumentClickListener(mExternalFileHelper)
hardwareKeySelectionView.selectionListener = { hardwareKey ->
hardwareKeyCheckBox.isChecked = true
hardwareKeySelectionView.error =
if (!HardwareKeyResponseHelper.isHardwareKeyAvailable(requireActivity(), hardwareKey)) {
// show hardware driver dialog if required
getString(R.string.error_driver_required, hardwareKey.toString())
} else {
null
}
}
val dialog = builder.create()
dialog.setOnShowListener { dialog1 ->
val positiveButton = (dialog1 as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
positiveButton.setOnClickListener {
if (passwordCheckBox != null && keyFileCheckBox!= null) {
dialog.setOnShowListener { dialog1 ->
val positiveButton = (dialog1 as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
positiveButton.setOnClickListener {
mMasterPassword = ""
mKeyFileUri = null
mHardwareKey = null
mMasterPassword = ""
mKeyFile = null
var error = verifyPassword() || verifyKeyFile()
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
error = true
if (allowNoMasterKey)
showNoKeyConfirmationDialog()
else {
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
}
}
if (!error) {
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
dismiss()
}
}
val negativeButton = dialog1.getButton(DialogInterface.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
mListener?.onAssignKeyDialogNegativeClick(retrieveMainCredential())
dismiss()
}
approveMainCredential()
}
val negativeButton = dialog1.getButton(DialogInterface.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
mListener?.onAssignKeyDialogNegativeClick(retrieveMainCredential())
dismiss()
}
}
@@ -184,67 +201,113 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
return super.onCreateDialog(savedInstanceState)
}
private fun approveMainCredential() {
val errorPassword = verifyPassword()
val errorKeyFile = verifyKeyFile()
val errorHardwareKey = verifyHardwareKey()
// Check all to fill error
var error = errorPassword || errorKeyFile || errorHardwareKey
val hardwareKey = hardwareKeySelectionView.hardwareKey
if (!error
&& (!passwordCheckBox.isChecked)
&& (!keyFileCheckBox.isChecked)
&& (!hardwareKeyCheckBox.isChecked)
) {
error = true
if (mAllowNoMasterKey) {
// show no key dialog if required
showNoKeyConfirmationDialog()
} else {
passwordRepeatTextInputLayout.error =
getString(R.string.error_disallow_no_credentials)
}
} else if (!error
&& mMasterPassword.isNullOrEmpty()
&& !keyFileCheckBox.isChecked
&& !hardwareKeyCheckBox.isChecked
) {
// show empty password dialog if required
error = true
showEmptyPasswordConfirmationDialog()
} else if (!error
&& hardwareKey != null
&& !HardwareKeyResponseHelper.isHardwareKeyAvailable(
requireActivity(), hardwareKey, false)
) {
// show hardware driver dialog if required
error = true
hardwareKeySelectionView.error =
getString(R.string.error_driver_required, hardwareKey.toString())
}
if (!error) {
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
dismiss()
}
}
private fun verifyPassword(): Boolean {
var error = false
passwordRepeatTextInputLayout.error = null
if (passwordCheckBox.isChecked) {
mMasterPassword = passwordView.passwordString
val confPassword = passwordRepeatView.text.toString()
// Verify that passwords match
if (mMasterPassword != confPassword) {
error = true
// Passwords do not match
passwordRepeatTextInputLayout.error = getString(R.string.error_pass_match)
}
}
return error
}
private fun verifyKeyFile(): Boolean {
var error = false
keyFileSelectionView.error = null
if (keyFileCheckBox.isChecked) {
keyFileSelectionView.uri?.let { uri ->
mKeyFileUri = uri
} ?: run {
error = true
keyFileSelectionView.error = getString(R.string.error_nokeyfile)
}
}
return error
}
private fun verifyHardwareKey(): Boolean {
var error = false
hardwareKeySelectionView.error = null
if (hardwareKeyCheckBox.isChecked) {
hardwareKeySelectionView.hardwareKey?.let { hardwareKey ->
mHardwareKey = hardwareKey
} ?: run {
error = true
hardwareKeySelectionView.error = getString(R.string.error_no_hardware_key)
}
}
return error
}
private fun retrieveMainCredential(): MainCredential {
val masterPassword = if (passwordCheckBox!!.isChecked) mMasterPassword else null
val keyFile = if (keyFileCheckBox!!.isChecked) mKeyFile else null
return MainCredential(masterPassword, keyFile)
val masterPassword = if (passwordCheckBox.isChecked) mMasterPassword else null
val keyFileUri = if (keyFileCheckBox.isChecked) mKeyFileUri else null
val hardwareKey = if (hardwareKeyCheckBox.isChecked) mHardwareKey else null
return MainCredential(masterPassword, keyFileUri, hardwareKey)
}
override fun onResume() {
super.onResume()
// To check checkboxes if a text is present
passwordView?.addTextChangedListener(passwordTextWatcher)
passwordView.addTextChangedListener(passwordTextWatcher)
}
override fun onPause() {
super.onPause()
passwordView?.removeTextChangedListener(passwordTextWatcher)
}
private fun verifyPassword(): Boolean {
var error = false
if (passwordCheckBox != null
&& passwordCheckBox!!.isChecked
&& passwordView != null
&& passwordRepeatView != null) {
mMasterPassword = passwordView!!.text.toString()
val confPassword = passwordRepeatView!!.text.toString()
// Verify that passwords match
if (mMasterPassword != confPassword) {
error = true
// Passwords do not match
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
}
if ((mMasterPassword == null
|| mMasterPassword!!.isEmpty())
&& (keyFileCheckBox == null
|| !keyFileCheckBox!!.isChecked
|| keyFileSelectionView?.uri == null)) {
error = true
showEmptyPasswordConfirmationDialog()
}
}
return error
}
private fun verifyKeyFile(): Boolean {
var error = false
if (keyFileCheckBox != null
&& keyFileCheckBox!!.isChecked) {
keyFileSelectionView?.uri?.let { uri ->
mKeyFile = uri
} ?: run {
error = true
keyFileSelectionView?.error = getString(R.string.error_nokeyfile)
}
}
return error
passwordView.removeTextChangedListener(passwordTextWatcher)
}
private fun showEmptyPasswordConfirmationDialog() {
@@ -252,10 +315,8 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.warning_empty_password)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (!verifyKeyFile()) {
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
this@SetMainCredentialDialogFragment.dismiss()
}
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
this@SetMainCredentialDialogFragment.dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
mEmptyPasswordConfirmationDialog = builder.create()
@@ -289,8 +350,8 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
})
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ ->
keyFileCheckBox?.isChecked = false
keyFileSelectionView?.uri = null
keyFileCheckBox.isChecked = false
keyFileSelectionView.uri = null
}
mEmptyKeyFileConfirmationDialog = builder.create()
mEmptyKeyFileConfirmationDialog?.show()

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,31 +39,22 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
val builder = AlertDialog.Builder(activity)
val stringBuilder = SpannableStringBuilder()
if (BuildConfig.CLOSED_STORE) {
if (BuildConfig.FULL_VERSION) {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
} else {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}
/*
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_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}
//}
builder.setMessage(stringBuilder)
// Create the AlertDialog object and return it
return builder.create()

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,13 +24,16 @@ 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.*
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
@@ -72,6 +74,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()
@@ -111,6 +117,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?) {
@@ -138,11 +157,9 @@ class EntryFragment: DatabaseFragment() {
setOnCopyActionClickListener { field ->
mClipboardHelper?.timeoutCopyToClipboard(
TemplateField.getLocalizedName(context, field.name),
field.protectedValue.stringValue,
getString(
R.string.copy_field,
TemplateField.getLocalizedName(context, field.name)
)
field.protectedValue.isProtected
)
}
}
@@ -231,8 +248,7 @@ class EntryFragment: DatabaseFragment() {
fun launchEntryCopyEducationAction() {
val appNameString = getString(R.string.app_name)
mClipboardHelper?.timeoutCopyToClipboard(appNameString,
getString(R.string.copy_field, appNameString))
mClipboardHelper?.timeoutCopyToClipboard(appNameString, appNameString)
}
companion object {

View File

@@ -26,14 +26,14 @@ class IconPickerFragment : DatabaseFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_icon_picker, container, false)
return inflater.inflate(R.layout.fragment_tabs_pagination, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.icon_picker_pager)
tabLayout = view.findViewById(R.id.icon_picker_tabs)
viewPager = view.findViewById(R.id.tabs_view_pager)
tabLayout = view.findViewById(R.id.tabs_layout)
resetAppTimeoutWhenViewFocusedOrChanged(view)
arguments?.apply {

View File

@@ -0,0 +1,139 @@
/*
* 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.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.fragment.app.activityViewModels
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.KeyGeneratorPagerAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class KeyGeneratorFragment : DatabaseFragment() {
private var keyGeneratorPagerAdapter: KeyGeneratorPagerAdapter? = null
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
private var mSelectedTab = KeyGeneratorTab.PASSWORD
private var mOnPageChangeCallback: ViewPager2.OnPageChangeCallback = object:
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
mSelectedTab = KeyGeneratorTab.getKeyGeneratorTabByPosition(position)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_tabs_pagination, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
keyGeneratorPagerAdapter = KeyGeneratorPagerAdapter(this, )
viewPager = view.findViewById(R.id.tabs_view_pager)
tabLayout = view.findViewById(R.id.tabs_layout)
viewPager.adapter = keyGeneratorPagerAdapter
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = getString(KeyGeneratorTab.getKeyGeneratorTabByPosition(position).stringId)
}.attach()
viewPager.registerOnPageChangeCallback(mOnPageChangeCallback)
resetAppTimeoutWhenViewFocusedOrChanged(view)
arguments?.apply {
if (containsKey(PASSWORD_TAB_ARG)) {
viewPager.currentItem = getInt(PASSWORD_TAB_ARG)
}
remove(PASSWORD_TAB_ARG)
}
mKeyGeneratorViewModel.requireKeyGeneration.observe(viewLifecycleOwner) {
when (mSelectedTab) {
KeyGeneratorTab.PASSWORD -> {
mKeyGeneratorViewModel.requirePasswordGeneration()
}
KeyGeneratorTab.PASSPHRASE -> {
mKeyGeneratorViewModel.requirePassphraseGeneration()
}
}
}
mKeyGeneratorViewModel.keyGeneratedValidated.observe(viewLifecycleOwner) {
when (mSelectedTab) {
KeyGeneratorTab.PASSWORD -> {
mKeyGeneratorViewModel.validatePasswordGenerated()
}
KeyGeneratorTab.PASSPHRASE -> {
mKeyGeneratorViewModel.validatePassphraseGenerated()
}
}
}
}
override fun onDestroyView() {
viewPager.unregisterOnPageChangeCallback(mOnPageChangeCallback)
super.onDestroyView()
}
override fun onDatabaseRetrieved(database: Database?) {
// Nothing here
}
enum class KeyGeneratorTab(@StringRes val stringId: Int) {
PASSWORD(R.string.password), PASSPHRASE(R.string.passphrase);
companion object {
fun getKeyGeneratorTabByPosition(position: Int): KeyGeneratorTab {
return when (position) {
0 -> PASSWORD
else -> PASSPHRASE
}
}
}
}
companion object {
private const val PASSWORD_TAB_ARG = "PASSWORD_TAB_ARG"
fun getInstance(keyGeneratorTab: KeyGeneratorTab): KeyGeneratorFragment {
val fragment = KeyGeneratorFragment()
fragment.arguments = Bundle().apply {
putInt(PASSWORD_TAB_ARG, keyGeneratorTab.ordinal)
}
return fragment
}
}
}

View File

@@ -0,0 +1,254 @@
/*
* 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.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.activityViewModels
import com.google.android.material.slider.Slider
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.password.PassphraseGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.view.PassKeyView
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class PassphraseGeneratorFragment : DatabaseFragment() {
private lateinit var passKeyView: PassKeyView
private lateinit var sliderWordCount: Slider
private lateinit var wordCountText: EditText
private lateinit var charactersCountText: TextView
private lateinit var wordSeparator: EditText
private lateinit var wordCaseSpinner: Spinner
private var minSliderWordCount: Int = 0
private var maxSliderWordCount: Int = 0
private var wordCaseAdapter: ArrayAdapter<String>? = null
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_generate_passphrase, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
passKeyView = view.findViewById(R.id.passphrase_view)
val passphraseCopyView: ImageView? = view.findViewById(R.id.passphrase_copy_button)
sliderWordCount = view.findViewById(R.id.slider_word_count)
wordCountText = view.findViewById(R.id.word_count)
charactersCountText = view.findViewById(R.id.character_count)
wordSeparator = view.findViewById(R.id.word_separator)
wordCaseSpinner = view.findViewById(R.id.word_case)
minSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_min)
maxSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_max)
contextThemed?.let { context ->
passphraseCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context)
passphraseCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(
getString(R.string.passphrase),
passKeyView.passwordString,
true
)
}
wordCaseAdapter = ArrayAdapter(context,
android.R.layout.simple_spinner_item, resources.getStringArray(R.array.word_case_array)).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
wordCaseSpinner.adapter = wordCaseAdapter
}
loadSettings()
var listenSlider = true
var listenEditText = true
sliderWordCount.addOnChangeListener { _, value, _ ->
try {
listenEditText = false
if (listenSlider) {
wordCountText.setText(value.toInt().toString())
}
} catch (e: Exception) {
Log.e(TAG, "Unable to set the word count value", e)
} finally {
listenEditText = true
}
}
sliderWordCount.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
// TODO upgrade material-components lib
// https://stackoverflow.com/questions/70873160/material-slider-onslidertouchlisteners-methods-can-only-be-called-from-within-t
@SuppressLint("RestrictedApi")
override fun onStartTrackingTouch(slider: Slider) {}
@SuppressLint("RestrictedApi")
override fun onStopTrackingTouch(slider: Slider) {
generatePassphrase()
}
})
wordCountText.doOnTextChanged { _, _, _, _ ->
if (listenEditText) {
try {
listenSlider = false
setSliderValue(getWordCount())
} catch (e: Exception) {
Log.e(TAG, "Unable to get the word count value", e)
} finally {
listenSlider = true
generatePassphrase()
}
}
}
wordSeparator.doOnTextChanged { _, _, _, _ ->
generatePassphrase()
}
wordCaseSpinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
generatePassphrase()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
generatePassphrase()
mKeyGeneratorViewModel.passphraseGeneratedValidated.observe(viewLifecycleOwner) {
mKeyGeneratorViewModel.setKeyGenerated(passKeyView.passwordString)
}
mKeyGeneratorViewModel.requirePassphraseGeneration.observe(viewLifecycleOwner) {
generatePassphrase()
}
resetAppTimeoutWhenViewFocusedOrChanged(view)
}
private fun getWordCount(): Int {
return try {
Integer.valueOf(wordCountText.text.toString())
} catch (numberException: NumberFormatException) {
minSliderWordCount
}
}
private fun setWordCount(wordCount: Int) {
setSliderValue(wordCount)
wordCountText.setText(wordCount.toString())
}
private fun setSliderValue(value: Int) {
when {
value < minSliderWordCount -> {
sliderWordCount.value = minSliderWordCount.toFloat()
}
value > maxSliderWordCount -> {
sliderWordCount.value = maxSliderWordCount.toFloat()
}
else -> {
sliderWordCount.value = value.toFloat()
}
}
}
private fun getWordSeparator(): String {
return wordSeparator.text.toString().ifEmpty { " " }
}
private fun getWordCase(): PassphraseGenerator.WordCase {
var wordCase = PassphraseGenerator.WordCase.LOWER_CASE
try {
wordCase = PassphraseGenerator.WordCase.getByOrdinal(wordCaseSpinner.selectedItemPosition)
} catch (caseException: Exception) {
Log.e(TAG, "Unable to retrieve the word case", caseException)
}
return wordCase
}
private fun setWordCase(wordCase: PassphraseGenerator.WordCase) {
wordCaseSpinner.setSelection(wordCase.ordinal)
}
private fun getSeparator(): String {
return wordSeparator.text?.toString() ?: ""
}
private fun setSeparator(separator: String) {
wordSeparator.setText(separator)
}
private fun generatePassphrase() {
var passphrase = ""
try {
passphrase = PassphraseGenerator().generatePassphrase(
getWordCount(),
getWordSeparator(),
getWordCase())
} catch (e: Exception) {
Log.e(TAG, "Unable to generate a passphrase", e)
}
passKeyView.passwordString = passphrase
charactersCountText.text = getString(R.string.character_count, passphrase.length)
}
override fun onDestroy() {
saveSettings()
super.onDestroy()
}
private fun saveSettings() {
context?.let { context ->
PreferencesUtil.setDefaultPassphraseWordCount(context, getWordCount())
PreferencesUtil.setDefaultPassphraseWordCase(context, getWordCase())
PreferencesUtil.setDefaultPassphraseSeparator(context, getSeparator())
}
}
private fun loadSettings() {
context?.let { context ->
setWordCount(PreferencesUtil.getDefaultPassphraseWordCount(context))
setWordCase(PreferencesUtil.getDefaultPassphraseWordCase(context))
setSeparator(PreferencesUtil.getDefaultPassphraseSeparator(context))
}
}
override fun onDatabaseRetrieved(database: Database?) {
// Nothing here
}
companion object {
private const val TAG = "PassphraseGnrtrFrgmt"
}
}

View File

@@ -0,0 +1,360 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.ImageView
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.activityViewModels
import com.google.android.material.slider.Slider
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.view.PassKeyView
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class PasswordGeneratorFragment : DatabaseFragment() {
private lateinit var passKeyView: PassKeyView
private lateinit var sliderLength: Slider
private lateinit var lengthEditView: EditText
private lateinit var uppercaseCompound: CompoundButton
private lateinit var lowercaseCompound: CompoundButton
private lateinit var digitsCompound: CompoundButton
private lateinit var minusCompound: CompoundButton
private lateinit var underlineCompound: CompoundButton
private lateinit var spaceCompound: CompoundButton
private lateinit var specialsCompound: CompoundButton
private lateinit var bracketsCompound: CompoundButton
private lateinit var extendedCompound: CompoundButton
private lateinit var considerCharsEditText: EditText
private lateinit var ignoreCharsEditText: EditText
private lateinit var atLeastOneCompound: CompoundButton
private lateinit var excludeAmbiguousCompound: CompoundButton
private var minLengthSlider: Int = 0
private var maxLengthSlider: Int = 0
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_generate_password, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
passKeyView = view.findViewById(R.id.password_view)
val passwordCopyView: ImageView? = view.findViewById(R.id.password_copy_button)
sliderLength = view.findViewById(R.id.slider_length)
lengthEditView = view.findViewById(R.id.length)
uppercaseCompound = view.findViewById(R.id.upperCase_filter)
lowercaseCompound = view.findViewById(R.id.lowerCase_filter)
digitsCompound = view.findViewById(R.id.digits_filter)
minusCompound = view.findViewById(R.id.minus_filter)
underlineCompound = view.findViewById(R.id.underline_filter)
spaceCompound = view.findViewById(R.id.space_filter)
specialsCompound = view.findViewById(R.id.special_filter)
bracketsCompound = view.findViewById(R.id.brackets_filter)
extendedCompound = view.findViewById(R.id.extendedASCII_filter)
considerCharsEditText = view.findViewById(R.id.consider_chars_filter)
ignoreCharsEditText = view.findViewById(R.id.ignore_chars_filter)
atLeastOneCompound = view.findViewById(R.id.atLeastOne_filter)
excludeAmbiguousCompound = view.findViewById(R.id.excludeAmbiguous_filter)
contextThemed?.let { context ->
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context)
passwordCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(
getString(R.string.password),
passKeyView.passwordString,
true
)
}
}
minLengthSlider = resources.getInteger(R.integer.password_generator_length_min)
maxLengthSlider = resources.getInteger(R.integer.password_generator_length_max)
loadSettings()
uppercaseCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
lowercaseCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
digitsCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
minusCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
underlineCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
spaceCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
specialsCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
bracketsCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
extendedCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
considerCharsEditText.doOnTextChanged { _, _, _, _ ->
generatePassword()
}
ignoreCharsEditText.doOnTextChanged { _, _, _, _ ->
generatePassword()
}
atLeastOneCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
excludeAmbiguousCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
var listenSlider = true
var listenEditText = true
sliderLength.addOnChangeListener { _, value, _ ->
try {
listenEditText = false
if (listenSlider) {
lengthEditView.setText(value.toInt().toString())
}
} catch (e: Exception) {
Log.e(TAG, "Unable to set the length value", e)
} finally {
listenEditText = true
}
}
sliderLength.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
// TODO upgrade material-components lib
// https://stackoverflow.com/questions/70873160/material-slider-onslidertouchlisteners-methods-can-only-be-called-from-within-t
@SuppressLint("RestrictedApi")
override fun onStartTrackingTouch(slider: Slider) {}
@SuppressLint("RestrictedApi")
override fun onStopTrackingTouch(slider: Slider) {
generatePassword()
}
})
lengthEditView.doOnTextChanged { _, _, _, _ ->
if (listenEditText) {
try {
listenSlider = false
setSliderValue(getPasswordLength())
} catch (e: Exception) {
Log.e(TAG, "Unable to get the length value", e)
} finally {
listenSlider = true
generatePassword()
}
}
}
// Pre-populate a password to possibly save the user a few clicks
generatePassword()
mKeyGeneratorViewModel.passwordGeneratedValidated.observe(viewLifecycleOwner) {
mKeyGeneratorViewModel.setKeyGenerated(passKeyView.passwordString)
}
mKeyGeneratorViewModel.requirePasswordGeneration.observe(viewLifecycleOwner) {
generatePassword()
}
resetAppTimeoutWhenViewFocusedOrChanged(view)
}
private fun getPasswordLength(): Int {
return try {
Integer.valueOf(lengthEditView.text.toString())
} catch (numberException: NumberFormatException) {
minLengthSlider
}
}
private fun setPasswordLength(passwordLength: Int) {
setSliderValue(passwordLength)
lengthEditView.setText(passwordLength.toString())
}
private fun getOptions(): Set<String> {
val optionsSet = mutableSetOf<String>()
if (uppercaseCompound.isChecked)
optionsSet.add(getString(R.string.value_password_uppercase))
if (lowercaseCompound.isChecked)
optionsSet.add(getString(R.string.value_password_lowercase))
if (digitsCompound.isChecked)
optionsSet.add(getString(R.string.value_password_digits))
if (minusCompound.isChecked)
optionsSet.add(getString(R.string.value_password_minus))
if (underlineCompound.isChecked)
optionsSet.add(getString(R.string.value_password_underline))
if (spaceCompound.isChecked)
optionsSet.add(getString(R.string.value_password_space))
if (specialsCompound.isChecked)
optionsSet.add(getString(R.string.value_password_special))
if (bracketsCompound.isChecked)
optionsSet.add(getString(R.string.value_password_brackets))
if (extendedCompound.isChecked)
optionsSet.add(getString(R.string.value_password_extended))
if (atLeastOneCompound.isChecked)
optionsSet.add(getString(R.string.value_password_atLeastOne))
if (excludeAmbiguousCompound.isChecked)
optionsSet.add(getString(R.string.value_password_excludeAmbiguous))
return optionsSet
}
private fun setOptions(options: Set<String>) {
uppercaseCompound.isChecked = false
lowercaseCompound.isChecked = false
digitsCompound.isChecked = false
minusCompound.isChecked = false
underlineCompound.isChecked = false
spaceCompound.isChecked = false
specialsCompound.isChecked = false
bracketsCompound.isChecked = false
extendedCompound.isChecked = false
atLeastOneCompound.isChecked = false
excludeAmbiguousCompound.isChecked = false
for (option in options) {
when (option) {
getString(R.string.value_password_uppercase) -> uppercaseCompound.isChecked = true
getString(R.string.value_password_lowercase) -> lowercaseCompound.isChecked = true
getString(R.string.value_password_digits) -> digitsCompound.isChecked = true
getString(R.string.value_password_minus) -> minusCompound.isChecked = true
getString(R.string.value_password_underline) -> underlineCompound.isChecked = true
getString(R.string.value_password_space) -> spaceCompound.isChecked = true
getString(R.string.value_password_special) -> specialsCompound.isChecked = true
getString(R.string.value_password_brackets) -> bracketsCompound.isChecked = true
getString(R.string.value_password_extended) -> extendedCompound.isChecked = true
getString(R.string.value_password_atLeastOne) -> atLeastOneCompound.isChecked = true
getString(R.string.value_password_excludeAmbiguous) -> excludeAmbiguousCompound.isChecked = true
}
}
}
private fun getConsiderChars(): String {
return considerCharsEditText.text.toString()
}
private fun setConsiderChars(chars: String) {
considerCharsEditText.setText(chars)
}
private fun getIgnoreChars(): String {
return ignoreCharsEditText.text.toString()
}
private fun setIgnoreChars(chars: String) {
ignoreCharsEditText.setText(chars)
}
private fun generatePassword() {
var password = ""
try {
password = PasswordGenerator(resources).generatePassword(getPasswordLength(),
uppercaseCompound.isChecked,
lowercaseCompound.isChecked,
digitsCompound.isChecked,
minusCompound.isChecked,
underlineCompound.isChecked,
spaceCompound.isChecked,
specialsCompound.isChecked,
bracketsCompound.isChecked,
extendedCompound.isChecked,
getConsiderChars(),
getIgnoreChars(),
atLeastOneCompound.isChecked,
excludeAmbiguousCompound.isChecked)
} catch (e: Exception) {
Log.e(TAG, "Unable to generate a password", e)
}
passKeyView.passwordString = password
}
override fun onDestroy() {
saveSettings()
super.onDestroy()
}
override fun onDatabaseRetrieved(database: Database?) {
// Nothing here
}
private fun saveSettings() {
context?.let { context ->
PreferencesUtil.setDefaultPasswordOptions(context, getOptions())
PreferencesUtil.setDefaultPasswordLength(context, getPasswordLength())
PreferencesUtil.setDefaultPasswordConsiderChars(context, getConsiderChars())
PreferencesUtil.setDefaultPasswordIgnoreChars(context, getIgnoreChars())
}
}
private fun loadSettings() {
context?.let { context ->
setOptions(PreferencesUtil.getDefaultPasswordOptions(context))
setPasswordLength(PreferencesUtil.getDefaultPasswordLength(context))
setConsiderChars(PreferencesUtil.getDefaultPasswordConsiderChars(context))
setIgnoreChars(PreferencesUtil.getDefaultPasswordIgnoreChars(context))
}
}
private fun setSliderValue(value: Int) {
when {
value < minLengthSlider -> {
sliderLength.value = minLengthSlider.toFloat()
}
value > maxLengthSlider -> {
sliderLength.value = maxLengthSlider.toFloat()
}
else -> {
sliderLength.value = value.toFloat()
}
}
}
companion object {
private const val TAG = "PasswordGeneratorFrgmt"
}
}

View File

@@ -85,7 +85,7 @@ object EntrySelectionHelper {
return intent.getParcelableExtra(KEY_SEARCH_INFO)
}
fun addRegisterInfoInIntent(intent: Intent, registerInfo: RegisterInfo?) {
private fun addRegisterInfoInIntent(intent: Intent, registerInfo: RegisterInfo?) {
registerInfo?.let {
intent.putExtra(KEY_REGISTER_INFO, it)
}
@@ -113,7 +113,7 @@ object EntrySelectionHelper {
?: SpecialMode.DEFAULT
}
fun addTypeModeInIntent(intent: Intent, typeMode: TypeMode) {
private fun addTypeModeInIntent(intent: Intent, typeMode: TypeMode) {
intent.putExtra(KEY_TYPE_MODE, typeMode as Serializable)
}

View File

@@ -56,7 +56,7 @@ class ExternalFileHelper {
fun buildOpenDocument(onFileSelected: ((uri: Uri?) -> Unit)?) {
val resultCallback = ActivityResultCallback<Uri> { result ->
val resultCallback = ActivityResultCallback<Uri?> { result ->
result?.let { uri ->
UriUtil.takeUriPermission(activity?.contentResolver, uri)
onFileSelected?.invoke(uri)
@@ -91,7 +91,7 @@ class ExternalFileHelper {
fun buildCreateDocument(typeString: String = "application/octet-stream",
onFileCreated: (fileCreated: Uri?)->Unit) {
val resultCallback = ActivityResultCallback<Uri> { result ->
val resultCallback = ActivityResultCallback<Uri?> { result ->
onFileCreated.invoke(result)
}
@@ -150,7 +150,7 @@ class ExternalFileHelper {
class OpenDocument : ActivityResultContracts.OpenDocument() {
@SuppressLint("InlinedApi")
override fun createIntent(context: Context, input: Array<out String>): Intent {
override fun createIntent(context: Context, input: Array<String>): Intent {
return super.createIntent(context, input).apply {
addCategory(Intent.CATEGORY_OPENABLE)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
@@ -178,11 +178,10 @@ class ExternalFileHelper {
}
}
class CreateDocument(private val typeString: String) : ActivityResultContracts.CreateDocument() {
class CreateDocument(typeString: String) : ActivityResultContracts.CreateDocument(typeString) {
override fun createIntent(context: Context, input: String): Intent {
return super.createIntent(context, input).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
}
}
}

View File

@@ -6,9 +6,10 @@ import androidx.activity.viewModels
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.ChallengeResponseViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
@@ -17,10 +18,12 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
protected var mDatabaseTaskProvider: DatabaseTaskProvider? = null
protected var mDatabase: Database? = null
private val mChallengeResponseViewModel: ChallengeResponseViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabaseTaskProvider = DatabaseTaskProvider(this)
mDatabaseTaskProvider = DatabaseTaskProvider(this, mChallengeResponseViewModel)
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
val databaseWasReloaded = database?.wasReloaded == true
@@ -36,6 +39,13 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
}
}
override fun onDestroy() {
mDatabaseTaskProvider?.destroy()
mDatabaseTaskProvider = null
mDatabase = null
super.onDestroy()
}
override fun onDatabaseRetrieved(database: Database?) {
mDatabase = database
mDatabaseViewModel.defineDatabase(database)

View File

@@ -24,11 +24,13 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
@@ -41,7 +43,7 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -431,7 +433,17 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
}
protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION))
// Ask confirmation if modification not saved
if (mDatabase?.dataModifiedSinceLastLoading == true) {
AlertDialog.Builder(this)
.setMessage(R.string.discard_changes)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.lock) { _, _ ->
sendBroadcast(Intent(LOCK_ACTION))
}.create().show()
} else {
sendBroadcast(Intent(LOCK_ACTION))
}
}
fun resetAppTimeout() {
@@ -467,25 +479,33 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
*/
@SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewTouchedOrFocused(context: Context, databaseLoaded: Boolean?) {
// Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout")
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false)
try {
// Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout")
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(
context,
databaseLoaded ?: false
)
}
}
false
}
setOnFocusChangeListener { _, _ ->
// Log.d(DatabaseLockActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(
context,
databaseLoaded ?: false
)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
}
}
false
}
setOnFocusChangeListener { _, _ ->
// Log.d(DatabaseLockActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
}
} catch (e: Exception) {
Log.e("AppTimeout", "Unable to reset app timeout", e)
}
}

View File

@@ -1,8 +1,6 @@
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
@@ -96,10 +94,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
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)
// Not using FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or finish() because kills the service
}
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -21,14 +21,21 @@ package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.WindowManager
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_PREFERENCE_CHANGED
import com.kunzisoft.keepass.settings.PreferencesUtil
/**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
@@ -81,8 +88,24 @@ abstract class StylishActivity : AppCompatActivity() {
setTheme(themeId)
}
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(onScreenshotModePrefListener)
}
private val onScreenshotModePrefListener = OnSharedPreferenceChangeListener { _, key ->
if (key != getString(R.string.enable_screenshot_mode_key)) return@OnSharedPreferenceChangeListener
setScreenshotMode(PreferencesUtil.isScreenshotModeEnabled(this))
}
private fun setScreenshotMode(isEnabled: Boolean) {
findViewById<View>(R.id.screenshot_mode_banner)?.visibility = if (isEnabled) VISIBLE else GONE
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
if (isEnabled) {
window.clearFlags(FLAG_SECURE)
} else {
window.setFlags(FLAG_SECURE, FLAG_SECURE)
}
}
override fun onResume() {
@@ -94,6 +117,7 @@ abstract class StylishActivity : AppCompatActivity() {
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
recreateActivity()
}
setScreenshotMode(PreferencesUtil.isScreenshotModeEnabled(this))
}
private fun recreateActivity() {

View File

@@ -0,0 +1,25 @@
package com.kunzisoft.keepass.adapters
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.kunzisoft.keepass.activities.fragments.PassphraseGeneratorFragment
import com.kunzisoft.keepass.activities.fragments.PasswordGeneratorFragment
import com.kunzisoft.keepass.activities.fragments.KeyGeneratorFragment
class KeyGeneratorPagerAdapter(fragment: Fragment)
: FragmentStateAdapter(fragment) {
private val passwordGeneratorFragment = PasswordGeneratorFragment()
private val passphraseGeneratorFragment = PassphraseGeneratorFragment()
override fun getItemCount(): Int {
return KeyGeneratorFragment.KeyGeneratorTab.values().size
}
override fun createFragment(position: Int): Fragment {
return when (KeyGeneratorFragment.KeyGeneratorTab.getKeyGeneratorTabByPosition(position)) {
KeyGeneratorFragment.KeyGeneratorTab.PASSWORD -> passwordGeneratorFragment
KeyGeneratorFragment.KeyGeneratorTab.PASSPHRASE -> passphraseGeneratorFragment
}
}
}

View File

@@ -21,13 +21,13 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.graphics.Color
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
@@ -516,18 +516,19 @@ class NodesAdapter (private val context: Context,
null -> {}
}
holder?.otpToken?.apply {
text = otpElement?.token
text = otpElement?.tokenString
setTextSize(mTextSizeUnit, mOtpTokenTextDefaultDimension, mPrefSizeMultiplier)
}
holder?.otpContainer?.setOnClickListener {
otpElement?.token?.let { token ->
Toast.makeText(
context,
context.getString(R.string.copy_field,
TemplateField.getLocalizedName(context, TemplateField.LABEL_TOKEN)),
Toast.LENGTH_LONG
).show()
mClipboardHelper.copyToClipboard(token)
try {
mClipboardHelper.copyToClipboard(
TemplateField.getLocalizedName(context, TemplateField.LABEL_TOKEN),
token
)
} catch (e: Exception) {
Log.e(TAG, "Unable to copy the OTP token", e)
}
}
}
}

View File

@@ -23,8 +23,15 @@ import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
import androidx.room.AutoMigration
@Database(version = 1, entities = [FileDatabaseHistoryEntity::class, CipherDatabaseEntity::class])
@Database(
version = 2,
entities = [FileDatabaseHistoryEntity::class, CipherDatabaseEntity::class],
autoMigrations = [
AutoMigration (from = 1, to = 2)
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun fileDatabaseHistoryDao(): FileDatabaseHistoryDao

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.app.database
import android.content.Context
import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.DatabaseFile
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.SingletonHolderParameter
@@ -44,6 +45,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
DatabaseFile(
databaseUri,
UriUtil.parse(fileDatabaseHistoryEntity?.keyFileUri),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity?.hardwareKey),
UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""),
fileDatabaseInfo.exists,
@@ -85,13 +87,14 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
|| !hideBrokenLocations) {
databaseFileListLoaded.add(
DatabaseFile(
UriUtil.parse(fileDatabaseHistoryEntity.databaseUri),
UriUtil.parse(fileDatabaseHistoryEntity.keyFileUri),
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
UriUtil.parse(fileDatabaseHistoryEntity.databaseUri),
UriUtil.parse(fileDatabaseHistoryEntity.keyFileUri),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity.hardwareKey),
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
)
}
@@ -107,11 +110,14 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
).execute()
}
fun addOrUpdateDatabaseUri(databaseUri: Uri, keyFileUri: Uri? = null,
fun addOrUpdateDatabaseUri(databaseUri: Uri,
keyFileUri: Uri? = null,
hardwareKey: HardwareKey? = null,
databaseFileAddedOrUpdatedResult: ((DatabaseFile?) -> Unit)? = null) {
addOrUpdateDatabaseFile(DatabaseFile(
databaseUri,
keyFileUri
databaseUri,
keyFileUri,
hardwareKey
), databaseFileAddedOrUpdatedResult)
}
@@ -130,6 +136,7 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
?: fileDatabaseHistoryRetrieve?.databaseAlias
?: "",
databaseFileToAddOrUpdate.keyFileUri?.toString(),
databaseFileToAddOrUpdate.hardwareKey?.value,
System.currentTimeMillis()
)
@@ -147,13 +154,14 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
val fileDatabaseInfo = FileDatabaseInfo(applicationContext,
fileDatabaseHistory.databaseUri)
DatabaseFile(
UriUtil.parse(fileDatabaseHistory.databaseUri),
UriUtil.parse(fileDatabaseHistory.keyFileUri),
UriUtil.decode(fileDatabaseHistory.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
UriUtil.parse(fileDatabaseHistory.databaseUri),
UriUtil.parse(fileDatabaseHistory.keyFileUri),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey),
UriUtil.decode(fileDatabaseHistory.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
}
},
@@ -172,10 +180,11 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
val returnValue = databaseFileHistoryDao.delete(fileDatabaseHistory)
if (returnValue > 0) {
DatabaseFile(
UriUtil.parse(fileDatabaseHistory.databaseUri),
UriUtil.parse(fileDatabaseHistory.keyFileUri),
UriUtil.decode(fileDatabaseHistory.databaseUri),
databaseFileToDelete.databaseAlias
UriUtil.parse(fileDatabaseHistory.databaseUri),
UriUtil.parse(fileDatabaseHistory.keyFileUri),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey),
UriUtil.decode(fileDatabaseHistory.databaseUri),
databaseFileToDelete.databaseAlias
)
} else {
null

View File

@@ -35,6 +35,9 @@ data class FileDatabaseHistoryEntity(
@ColumnInfo(name = "keyfile_uri")
var keyFileUri: String?,
@ColumnInfo(name = "hardware_key")
var hardwareKey: String?,
@ColumnInfo(name = "updated")
val updated: Long
) {

View File

@@ -26,7 +26,7 @@ import kotlinx.coroutines.*
*/
class IOActionTask<T>(
private val action: () -> T ,
private val afterActionDatabaseListener: ((T?) -> Unit)? = null) {
private val afterActionListener: ((T?) -> Unit)? = null) {
private val mainScope = CoroutineScope(Dispatchers.Main)
@@ -42,7 +42,7 @@ class IOActionTask<T>(
}
}
withContext(Dispatchers.Main) {
afterActionDatabaseListener?.invoke(asyncResult.await())
afterActionListener?.invoke(asyncResult.await())
}
}
}

View File

@@ -34,17 +34,17 @@ import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.getkeepsafe.taptargetview.TapTargetView
import com.kunzisoft.keepass.R
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.database.exception.UnknownDatabaseLocationException
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
@@ -396,7 +396,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
} ?: deleteEncryptedDatabaseKey()
}
} ?: throw IODatabaseException()
} ?: throw UnknownDatabaseLocationException()
} ?: throw Exception("AdvancedUnlockManager not initialized")
}
@@ -579,10 +579,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()
}
}
}
@@ -608,26 +611,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
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,

View File

@@ -124,7 +124,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
private fun getSecretKey(): SecretKey? {
@Synchronized private fun getSecretKey(): SecretKey? {
if (!isKeyManagerInitialized) {
return null
}
@@ -141,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
@@ -173,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
@@ -213,7 +213,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun encryptData(value: ByteArray) {
@Synchronized fun encryptData(value: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
@@ -229,12 +229,12 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun initDecryptData(ivSpecValue: ByteArray,
@Synchronized fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
initDecryptData(ivSpecValue, actionIfCypherInit, true)
}
private fun initDecryptData(ivSpecValue: ByteArray,
@Synchronized private fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean = true) {
if (!isKeyManagerInitialized) {
@@ -278,7 +278,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun decryptData(encryptedValue: ByteArray) {
@Synchronized fun decryptData(encryptedValue: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
@@ -296,7 +296,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun deleteKeystoreKey() {
@Synchronized fun deleteKeystoreKey() {
try {
keyStore?.load(null)
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
@@ -306,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
@@ -346,7 +346,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun closeBiometricPrompt() {
@Synchronized fun closeBiometricPrompt() {
biometricPrompt?.cancelAuthentication()
}
@@ -403,13 +403,11 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
fun isDeviceSecure(context: Context): Boolean {
val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
return keyguardManager?.isDeviceSecure ?: false
return ContextCompat.getSystemService(context, KeyguardManager::class.java)
?.isDeviceSecure ?: false
}
@RequiresApi(api = Build.VERSION_CODES.M)
fun biometricUnlockSupported(context: Context): Boolean {
val biometricCanAuthenticate = try {
BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG)
@@ -430,28 +428,23 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
)
}
@RequiresApi(api = Build.VERSION_CODES.M)
fun deviceCredentialUnlockSupported(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
(biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ContextCompat.getSystemService(context, KeyguardManager::class.java)?.apply {
return isDeviceSecure
}
} else {
true
}
return false
}
/**
* Remove entry key in keystore
*/
@RequiresApi(api = Build.VERSION_CODES.M)
fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity,
advancedCallback: AdvancedUnlockErrorCallback) {
AdvancedUnlockManager{ fragmentActivity }.apply {

View File

@@ -24,15 +24,16 @@ import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.database.element.MainCredential
open class AssignMainCredentialInDatabaseRunnable (
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
protected val mMainCredential: MainCredential)
: SaveDatabaseRunnable(context, database, true) {
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
mainCredential: MainCredential,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, true, mainCredential, challengeResponseRetriever) {
private var mBackupKey: ByteArray? = null
@@ -40,10 +41,7 @@ open class AssignMainCredentialInDatabaseRunnable (
// Set key
try {
mBackupKey = ByteArray(database.masterKey.size)
System.arraycopy(database.masterKey, 0, mBackupKey!!, 0, mBackupKey!!.size)
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mMainCredential.keyFileUri)
database.assignMasterKey(mMainCredential.masterPassword, uriInputStream)
database.masterKey.copyInto(mBackupKey!!)
} catch (e: Exception) {
erase(mBackupKey)
setError(e)

View File

@@ -24,7 +24,8 @@ import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
class CreateDatabaseRunnable(context: Context,
@@ -33,9 +34,10 @@ class CreateDatabaseRunnable(context: Context,
private val databaseName: String,
private val rootName: String,
private val templateGroupName: String?,
mainCredential: MainCredential,
val mainCredential: MainCredential,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private val createDatabaseResult: ((Result) -> Unit)?)
: AssignMainCredentialInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential) {
: AssignMainCredentialInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential, challengeResponseRetriever) {
override fun onStartRun() {
try {
@@ -58,8 +60,11 @@ class CreateDatabaseRunnable(context: Context,
// Add database to recent files
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context.applicationContext)
.addOrUpdateDatabaseUri(mDatabaseUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mMainCredential.keyFileUri else null)
.addOrUpdateDatabaseUri(
mDatabaseUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mainCredential.keyFileUri else null,
if (PreferencesUtil.rememberHardwareKey(context)) mainCredential.hardwareKey else null,
)
}
// Register the current time to init the lock timer

View File

@@ -38,12 +38,16 @@ import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
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.MainCredential
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.InvalidCredentialsDatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.ProgressMessage
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
@@ -82,6 +86,7 @@ import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import com.kunzisoft.keepass.viewmodels.ChallengeResponseViewModel
import kotlinx.coroutines.launch
import java.util.*
@@ -92,7 +97,6 @@ import java.util.*
class DatabaseTaskProvider {
private var activity: FragmentActivity? = null
private var service: Service? = null
private var context: Context
var onDatabaseRetrieved: ((database: Database?) -> Unit)? = null
@@ -111,30 +115,80 @@ class DatabaseTaskProvider {
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
constructor(activity: FragmentActivity) {
private var mChallengeResponseViewModel: ChallengeResponseViewModel? = null
constructor(activity: FragmentActivity,
challengeResponseViewModel: ChallengeResponseViewModel) {
this.activity = activity
this.context = activity
this.intentDatabaseTask = Intent(activity.applicationContext,
DatabaseTaskNotificationService::class.java)
// ViewModel used to keep response if activity recreated
this.mChallengeResponseViewModel = challengeResponseViewModel
// To manage hardware key challenge response
val hardwareKeyResponseHelper = HardwareKeyResponseHelper(activity)
hardwareKeyResponseHelper.buildHardwareKeyResponse { responseData, _ ->
// TODO Verify database
// Send to view model in case activity is restarted and not yet connected to service
challengeResponseViewModel.respond(responseData ?: ByteArray(0))
}
challengeResponseViewModel.dataResponded.observe(activity) { response ->
// Consume the response
if (response != null) {
val binder = mBinder
if (binder != null) {
binder.getService().respondToChallenge(response)
challengeResponseViewModel.consumeResponse()
}
}
}
this.requestChallengeListener = object: DatabaseTaskNotificationService.RequestChallengeListener {
override fun onChallengeResponseRequested(hardwareKey: HardwareKey, seed: ByteArray?) {
if (HardwareKeyResponseHelper.isHardwareKeyAvailable(activity, hardwareKey)) {
hardwareKeyResponseHelper.launchChallengeForResponse(hardwareKey, seed)
} else {
throw InvalidCredentialsDatabaseException(
context.getString(R.string.error_driver_required, hardwareKey.toString())
)
}
}
}
}
constructor(service: Service) {
this.service = service
this.context = service
this.intentDatabaseTask = Intent(service.applicationContext,
DatabaseTaskNotificationService::class.java)
}
fun destroy() {
this.activity = null
this.onDatabaseRetrieved = null
this.onActionFinish = null
this.databaseTaskBroadcastReceiver = null
this.mBinder = null
this.serviceConnection = null
this.progressTaskDialogFragment = null
this.databaseChangedDialogFragment = null
this.mChallengeResponseViewModel = null
}
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) {
startDialog(titleId, messageId, warningId)
override fun onStartAction(database: Database,
progressMessage: ProgressMessage) {
startDialog(progressMessage)
}
override fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) {
updateDialog(titleId, messageId, warningId)
override fun onUpdateAction(database: Database,
progressMessage: ProgressMessage) {
updateDialog(progressMessage)
}
override fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result) {
override fun onStopAction(database: Database,
actionTask: String,
result: ActionRunnable.Result) {
onActionFinish?.invoke(database, actionTask, result)
// Remove the progress task
stopDialog()
@@ -181,9 +235,9 @@ class DatabaseTaskProvider {
}
}
private fun startDialog(titleId: Int? = null,
messageId: Int? = null,
warningId: Int? = null) {
private var requestChallengeListener: DatabaseTaskNotificationService.RequestChallengeListener? = null
private fun startDialog(progressMessage: ProgressMessage) {
activity?.let { activity ->
activity.lifecycleScope.launch {
if (progressTaskDialogFragment == null) {
@@ -197,22 +251,17 @@ class DatabaseTaskProvider {
PROGRESS_TASK_DIALOG_TAG
)
}
updateDialog(titleId, messageId, warningId)
updateDialog(progressMessage)
}
}
}
private fun updateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
private fun updateDialog(progressMessage: ProgressMessage) {
progressTaskDialogFragment?.apply {
titleId?.let {
updateTitle(it)
}
messageId?.let {
updateMessage(it)
}
warningId?.let {
updateWarning(it)
}
updateTitle(progressMessage.titleId)
updateMessage(progressMessage.messageId)
updateWarning(progressMessage.warningId)
setCancellable(progressMessage.cancelable)
}
}
@@ -226,29 +275,42 @@ class DatabaseTaskProvider {
serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addDatabaseListener(databaseListener)
addDatabaseFileInfoListener(databaseInfoListener)
addActionTaskListener(actionTaskListener)
addServiceListeners(this)
getService().checkDatabase()
getService().checkDatabaseInfo()
getService().checkAction()
}
mChallengeResponseViewModel?.resendResponse()
}
override fun onServiceDisconnected(name: ComponentName?) {
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
removeServiceListeners(mBinder)
mBinder = null
}
}
}
}
private fun addServiceListeners(service: DatabaseTaskNotificationService.ActionTaskBinder?) {
service?.addDatabaseListener(databaseListener)
service?.addDatabaseFileInfoListener(databaseInfoListener)
service?.addActionTaskListener(actionTaskListener)
requestChallengeListener?.let {
service?.addRequestChallengeListener(it)
}
}
private fun removeServiceListeners(service: DatabaseTaskNotificationService.ActionTaskBinder?) {
service?.removeActionTaskListener(actionTaskListener)
service?.removeDatabaseFileInfoListener(databaseInfoListener)
service?.removeDatabaseListener(databaseListener)
service?.removeRequestChallengeListener()
}
private fun bindService() {
initServiceConnection()
serviceConnection?.let {
context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT)
context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_IMPORTANT or BIND_ABOVE_CLIENT)
}
}
@@ -262,10 +324,6 @@ class DatabaseTaskProvider {
serviceConnection = null
}
fun isBinded(): Boolean {
return mBinder != null
}
fun registerProgressTask() {
stopDialog()
@@ -299,9 +357,7 @@ class DatabaseTaskProvider {
fun unregisterProgressTask() {
stopDialog()
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
removeServiceListeners(mBinder)
mBinder = null
unBindService()
@@ -321,7 +377,7 @@ class DatabaseTaskProvider {
context.startService(intentDatabaseTask)
} catch (e: Exception) {
Log.e(TAG, "Unable to perform database action", e)
Toast.makeText(activity, R.string.error_start_database_action, Toast.LENGTH_LONG).show()
Toast.makeText(context, R.string.error_start_database_action, Toast.LENGTH_LONG).show()
}
}
@@ -332,7 +388,8 @@ class DatabaseTaskProvider {
*/
fun startDatabaseCreate(databaseUri: Uri,
mainCredential: MainCredential) {
mainCredential: MainCredential
) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
@@ -385,7 +442,8 @@ class DatabaseTaskProvider {
}
fun startDatabaseAssignPassword(databaseUri: Uri,
mainCredential: MainCredential) {
mainCredential: MainCredential
) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)

View File

@@ -25,9 +25,10 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.DatabaseInputException
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
@@ -35,8 +36,9 @@ import com.kunzisoft.keepass.utils.UriUtil
class LoadDatabaseRunnable(private val context: Context,
private val mDatabase: Database,
private val mUri: Uri,
private val mDatabaseUri: Uri,
private val mMainCredential: MainCredential,
private val mChallengeResponseRetriever: (hardwareKey: HardwareKey, seed: ByteArray?) -> ByteArray,
private val mReadonly: Boolean,
private val mCipherEncryptDatabase: CipherEncryptDatabase?,
private val mFixDuplicateUUID: Boolean,
@@ -51,18 +53,21 @@ class LoadDatabaseRunnable(private val context: Context,
override fun onActionRun() {
try {
mDatabase.loadData(mUri,
mMainCredential,
mReadonly,
context.contentResolver,
UriUtil.getBinaryDir(context),
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
mFixDuplicateUUID,
progressTaskUpdater)
mDatabase.loadData(
context.contentResolver,
mDatabaseUri,
mMainCredential,
mChallengeResponseRetriever,
mReadonly,
UriUtil.getBinaryDir(context),
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
mFixDuplicateUUID,
progressTaskUpdater
)
}
catch (e: LoadDatabaseException) {
catch (e: DatabaseInputException) {
setError(e)
}
@@ -70,8 +75,11 @@ class LoadDatabaseRunnable(private val context: Context,
// Save keyFile in app database
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context)
.addOrUpdateDatabaseUri(mUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mMainCredential.keyFileUri else null)
.addOrUpdateDatabaseUri(
mDatabaseUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mMainCredential.keyFileUri else null,
if (PreferencesUtil.rememberHardwareKey(context)) mMainCredential.hardwareKey else null,
)
}
// Register the biometric

View File

@@ -22,9 +22,10 @@ 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.MainCredential
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.database.exception.DatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
@@ -33,6 +34,7 @@ class MergeDatabaseRunnable(private val context: Context,
private val mDatabase: Database,
private val mDatabaseToMergeUri: Uri?,
private val mDatabaseToMergeMainCredential: MainCredential?,
private val mDatabaseToMergeChallengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
: ActionRunnable() {
@@ -43,15 +45,17 @@ class MergeDatabaseRunnable(private val context: Context,
override fun onActionRun() {
try {
mDatabase.mergeData(mDatabaseToMergeUri,
mDatabaseToMergeMainCredential,
mDatabase.mergeData(
context.contentResolver,
mDatabaseToMergeUri,
mDatabaseToMergeMainCredential,
mDatabaseToMergeChallengeResponseRetriever,
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
progressTaskUpdater
)
} catch (e: LoadDatabaseException) {
} catch (e: DatabaseException) {
setError(e)
}

View File

@@ -22,7 +22,7 @@ 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.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
@@ -47,7 +47,7 @@ class ReloadDatabaseRunnable(private val context: Context,
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
progressTaskUpdater)
} catch (e: LoadDatabaseException) {
} catch (e: DatabaseException) {
setError(e)
}

View File

@@ -21,12 +21,14 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.hardware.HardwareKey
class RemoveUnlinkedDataDatabaseRunnable (
context: Context,
database: Database,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onActionRun() {
try {

View File

@@ -23,11 +23,15 @@ 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.hardware.HardwareKey
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable
open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
private var saveDatabase: Boolean,
private var mainCredential: MainCredential?, // If null, uses composite Key
private var challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private var databaseCopyUri: Uri? = null)
: ActionRunnable() {
@@ -39,7 +43,12 @@ open class SaveDatabaseRunnable(protected var context: Context,
database.checkVersion()
if (saveDatabase && result.isSuccess) {
try {
database.saveData(databaseCopyUri, context.contentResolver)
database.saveData(
context.contentResolver,
context.cacheDir,
databaseCopyUri,
mainCredential,
challengeResponseRetriever)
} catch (e: DatabaseException) {
setError(e)
}

View File

@@ -22,14 +22,16 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.hardware.HardwareKey
class UpdateCompressionBinariesDatabaseRunnable (
context: Context,
database: Database,
private val oldCompressionAlgorithm: CompressionAlgorithm,
private val newCompressionAlgorithm: CompressionAlgorithm,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onStartRun() {
// Set new compression

View File

@@ -23,14 +23,16 @@ import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.hardware.HardwareKey
class DeleteEntryHistoryDatabaseRunnable (
context: Context,
database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onStartRun() {
try {

View File

@@ -23,6 +23,7 @@ import android.content.Context
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.tasks.ActionRunnable
class RestoreEntryHistoryDatabaseRunnable (
@@ -30,7 +31,8 @@ class RestoreEntryHistoryDatabaseRunnable (
private val database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
private val saveDatabase: Boolean)
private val saveDatabase: Boolean,
private val challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionRunnable() {
private var updateEntryRunnable: UpdateEntryRunnable? = null
@@ -43,12 +45,15 @@ class RestoreEntryHistoryDatabaseRunnable (
historyToRestore.addEntryToHistory(it)
}
// Update the entry with the fresh formatted entry to restore
updateEntryRunnable = UpdateEntryRunnable(context,
database,
mainEntry,
historyToRestore,
saveDatabase,
null)
updateEntryRunnable = UpdateEntryRunnable(
context,
database,
mainEntry,
historyToRestore,
saveDatabase,
null,
challengeResponseRetriever
)
updateEntryRunnable?.onStartRun()

View File

@@ -22,13 +22,15 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.hardware.HardwareKey
abstract class ActionNodeDatabaseRunnable(
context: Context,
database: Database,
private val afterActionNodesFinish: AfterActionNodesFinish?,
save: Boolean)
: SaveDatabaseRunnable(context, database, save) {
save: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, save, null, challengeResponseRetriever) {
/**
* Function do to a node action

View File

@@ -24,6 +24,7 @@ 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.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class AddEntryRunnable constructor(
context: Context,
@@ -31,8 +32,9 @@ class AddEntryRunnable constructor(
private val mNewEntry: Entry,
private val mParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
mNewEntry.touch(modified = true, touchParents = true)

View File

@@ -23,6 +23,7 @@ import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class AddGroupRunnable constructor(
context: Context,
@@ -30,8 +31,9 @@ class AddGroupRunnable constructor(
private val mNewGroup: Group,
private val mParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
mNewGroup.touch(modified = true, touchParents = true)

View File

@@ -21,11 +21,14 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.CopyEntryDatabaseException
import com.kunzisoft.keepass.database.exception.CopyGroupDatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
class CopyNodesRunnable constructor(
context: Context,
@@ -33,8 +36,9 @@ class CopyNodesRunnable constructor(
private val mNodesToCopy: List<Node>,
private val mNewParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
private var mEntriesCopied = ArrayList<Entry>()

View File

@@ -20,16 +20,20 @@
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.hardware.HardwareKey
class DeleteNodesRunnable(context: Context,
database: Database,
private val mNodesToDelete: List<Node>,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterActionNodesFinish: AfterActionNodesFinish,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
private var mOldParent: Group? = null
private var mCanRecycle: Boolean = false

View File

@@ -21,11 +21,14 @@ package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.MoveEntryDatabaseException
import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
class MoveNodesRunnable constructor(
context: Context,
@@ -33,8 +36,9 @@ class MoveNodesRunnable constructor(
private val mNodesToMove: List<Node>,
private val mNewParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
private var mOldParent: Group? = null

View File

@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class UpdateEntryRunnable constructor(
context: Context,
@@ -31,8 +32,9 @@ class UpdateEntryRunnable constructor(
private val mOldEntry: Entry,
private val mNewEntry: Entry,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
if (mOldEntry.nodeId == mNewEntry.nodeId) {

View File

@@ -23,6 +23,7 @@ import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class UpdateGroupRunnable constructor(
context: Context,
@@ -30,8 +31,9 @@ class UpdateGroupRunnable constructor(
private val mOldGroup: Group,
private val mNewGroup: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
if (mOldGroup.nodeId == mNewGroup.nodeId) {

View File

@@ -0,0 +1,34 @@
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.hardware.HardwareKey
data class CompositeKey(var passwordData: ByteArray? = null,
var keyFileData: ByteArray? = null,
var hardwareKey: HardwareKey? = null) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CompositeKey
if (passwordData != null) {
if (other.passwordData == null) return false
if (!passwordData.contentEquals(other.passwordData)) return false
} else if (other.passwordData != null) return false
if (keyFileData != null) {
if (other.keyFileData == null) return false
if (!keyFileData.contentEquals(other.keyFileData)) return false
} else if (other.keyFileData != null) return false
if (hardwareKey != other.hardwareKey) return false
return true
}
override fun hashCode(): Int {
var result = passwordData?.contentHashCode() ?: 0
result = 31 * result + (keyFileData?.contentHashCode() ?: 0)
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
return result
}
}

View File

@@ -54,12 +54,10 @@ 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.hardware.HardwareKey
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.utils.*
import java.io.*
import java.util.*
@@ -73,7 +71,7 @@ class Database {
var fileUri: Uri? = null
private set
private var mSearchHelper: SearchHelper? = null
private var mSearchHelper: SearchHelper = SearchHelper()
var isReadOnly = false
@@ -384,10 +382,14 @@ class Database {
set(masterKey) {
mDatabaseKDB?.masterKey = masterKey
mDatabaseKDBX?.masterKey = masterKey
mDatabaseKDBX?.keyLastChanged = DateInstant()
mDatabaseKDBX?.settingsChanged = DateInstant()
dataModifiedSinceLastLoading = true
}
val transformSeed: ByteArray?
get() = mDatabaseKDB?.transformSeed ?: mDatabaseKDBX?.transformSeed
var rootGroup: Group?
get() {
mDatabaseKDB?.rootGroup?.let {
@@ -557,79 +559,28 @@ class Database {
this.dataModifiedSinceLastLoading = false
}
@Throws(LoadDatabaseException::class)
private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
openDatabaseKDB: (InputStream) -> DatabaseKDB,
openDatabaseKDBX: (InputStream) -> DatabaseKDBX) {
var databaseInputStream: InputStream? = null
try {
// Load Data, pass Uris as InputStreams
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
?: throw IOException("Database input stream cannot be retrieve")
databaseInputStream = BufferedInputStream(databaseStream)
if (!databaseInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
// We'll end up reading 8 bytes to identify the header. Might as well use two extra.
databaseInputStream.mark(10)
// Get the file directory to save the attachments
val sig1 = databaseInputStream.readBytes4ToUInt()
val sig2 = databaseInputStream.readBytes4ToUInt()
// Return to the start
databaseInputStream.reset()
when {
// Header of database KDB
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(openDatabaseKDB(databaseInputStream))
// Header of database KDBX
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(openDatabaseKDBX(databaseInputStream))
// Header not recognized
else -> throw SignatureDatabaseException()
}
this.mSearchHelper = SearchHelper()
loaded = true
} catch (e: LoadDatabaseException) {
throw e
} catch (e: Exception) {
throw LoadDatabaseException(e)
} finally {
databaseInputStream?.close()
}
}
@Throws(LoadDatabaseException::class)
fun loadData(uri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
contentResolver: ContentResolver,
cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
@Throws(DatabaseInputException::class)
fun loadData(
contentResolver: ContentResolver,
databaseUri: Uri,
mainCredential: MainCredential,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
readOnly: Boolean,
cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?
) {
// Save database URI
this.fileUri = uri
this.fileUri = databaseUri
// Check if the file is writable
this.isReadOnly = readOnly
// Pass KeyFile Uri as InputStreams
var keyFileInputStream: InputStream? = null
try {
// Get keyFile inputStream
mainCredential.keyFileUri?.let { keyFile ->
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
}
// Read database stream for the first time
readDatabaseStream(contentResolver, uri,
readDatabaseStream(contentResolver, databaseUri,
{ databaseInputStream ->
val databaseKDB = DatabaseKDB().apply {
binaryCache.cacheDirectory = cacheDirectory
@@ -639,12 +590,12 @@ class Database {
.openDatabase(databaseInputStream,
progressTaskUpdater
) {
databaseKDB.retrieveMasterKey(
mainCredential.masterPassword,
keyFileInputStream
databaseKDB.deriveMasterKey(
contentResolver,
mainCredential
)
}
databaseKDB
setDatabaseKDB(databaseKDB)
},
{ databaseInputStream ->
val databaseKDBX = DatabaseKDBX().apply {
@@ -655,23 +606,23 @@ class Database {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
openDatabase(databaseInputStream,
progressTaskUpdater) {
databaseKDBX.retrieveMasterKey(
mainCredential.masterPassword,
keyFileInputStream,
databaseKDBX.deriveMasterKey(
contentResolver,
mainCredential,
challengeResponseRetriever
)
}
}
databaseKDBX
setDatabaseKDBX(databaseKDBX)
}
)
} catch (e: FileNotFoundException) {
throw FileNotFoundDatabaseException("Unable to load the keyfile")
} catch (e: LoadDatabaseException) {
throw e
loaded = true
} catch (e: Exception) {
throw LoadDatabaseException(e)
Log.e(TAG, "Unable to load the database")
if (e is DatabaseInputException)
throw e
throw DatabaseInputException(e)
} finally {
keyFileInputStream?.close()
dataModifiedSinceLastLoading = false
}
}
@@ -680,48 +631,44 @@ class Database {
return mDatabaseKDBX != null
}
@Throws(LoadDatabaseException::class)
fun mergeData(databaseToMergeUri: Uri?,
databaseToMergeMainCredential: MainCredential?,
contentResolver: ContentResolver,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
@Throws(DatabaseInputException::class)
fun mergeData(
contentResolver: ContentResolver,
databaseToMergeUri: Uri?,
databaseToMergeMainCredential: MainCredential?,
databaseToMergeChallengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
progressTaskUpdater: ProgressTaskUpdater?
) {
mDatabaseKDB?.let {
throw IODatabaseException("Unable to merge from a database V1")
throw MergeDatabaseKDBException()
}
// 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,
readDatabaseStream(contentResolver, databaseUri,
{ databaseInputStream ->
val databaseToMergeKDB = DatabaseKDB()
DatabaseInputKDB(databaseToMergeKDB)
.openDatabase(databaseInputStream, progressTaskUpdater) {
if (databaseToMergeMainCredential != null) {
databaseToMergeKDB.retrieveMasterKey(
databaseToMergeMainCredential.masterPassword,
keyFileInputStream,
databaseToMergeKDB.deriveMasterKey(
contentResolver,
databaseToMergeMainCredential
)
} else {
databaseToMergeKDB.masterKey = masterKey
this@Database.mDatabaseKDB?.let { thisDatabaseKDB ->
databaseToMergeKDB.copyMasterKeyFrom(thisDatabaseKDB)
}
}
}
databaseToMergeKDB
databaseToMerge.setDatabaseKDB(databaseToMergeKDB)
},
{ databaseInputStream ->
val databaseToMergeKDBX = DatabaseKDBX()
@@ -729,18 +676,22 @@ class Database {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
openDatabase(databaseInputStream, progressTaskUpdater) {
if (databaseToMergeMainCredential != null) {
databaseToMergeKDBX.retrieveMasterKey(
databaseToMergeMainCredential.masterPassword,
keyFileInputStream,
databaseToMergeKDBX.deriveMasterKey(
contentResolver,
databaseToMergeMainCredential,
databaseToMergeChallengeResponseRetriever
)
} else {
databaseToMergeKDBX.masterKey = masterKey
this@Database.mDatabaseKDBX?.let { thisDatabaseKDBX ->
databaseToMergeKDBX.copyMasterKeyFrom(thisDatabaseKDBX)
}
}
}
}
databaseToMergeKDBX
databaseToMerge.setDatabaseKDBX(databaseToMergeKDBX)
}
)
loaded = true
mDatabaseKDBX?.let { currentDatabaseKDBX ->
val databaseMerger = DatabaseKDBXMerger(currentDatabaseKDBX).apply {
@@ -760,24 +711,24 @@ class Database {
}
}
} else {
throw IODatabaseException("Database URI is null, database cannot be merged")
throw UnknownDatabaseLocationException()
}
} catch (e: FileNotFoundException) {
throw FileNotFoundDatabaseException("Unable to load the keyfile")
} catch (e: LoadDatabaseException) {
throw e
} catch (e: Exception) {
throw LoadDatabaseException(e)
Log.e(TAG, "Unable to merge the database")
if (e is DatabaseException)
throw e
throw DatabaseInputException(e)
} finally {
keyFileInputStream?.close()
databaseToMerge.clearAndClose()
}
}
@Throws(LoadDatabaseException::class)
fun reloadData(contentResolver: ContentResolver,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
@Throws(DatabaseInputException::class)
fun reloadData(
contentResolver: ContentResolver,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
progressTaskUpdater: ProgressTaskUpdater?
) {
// Retrieve the stream from the old database URI
try {
@@ -791,9 +742,11 @@ class Database {
}
DatabaseInputKDB(databaseKDB)
.openDatabase(databaseInputStream, progressTaskUpdater) {
databaseKDB.masterKey = masterKey
this@Database.mDatabaseKDB?.let { thisDatabaseKDB ->
databaseKDB.copyMasterKeyFrom(thisDatabaseKDB)
}
}
databaseKDB
setDatabaseKDB(databaseKDB)
},
{ databaseInputStream ->
val databaseKDBX = DatabaseKDBX()
@@ -803,26 +756,144 @@ class Database {
DatabaseInputKDBX(databaseKDBX).apply {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
openDatabase(databaseInputStream, progressTaskUpdater) {
databaseKDBX.masterKey = masterKey
this@Database.mDatabaseKDBX?.let { thisDatabaseKDBX ->
databaseKDBX.copyMasterKeyFrom(thisDatabaseKDBX)
}
}
}
databaseKDBX
setDatabaseKDBX(databaseKDBX)
}
)
loaded = true
} else {
throw IODatabaseException("Database URI is null, database cannot be reloaded")
throw UnknownDatabaseLocationException()
}
} catch (e: FileNotFoundException) {
throw FileNotFoundDatabaseException("Unable to load the keyfile")
} catch (e: LoadDatabaseException) {
throw e
} catch (e: Exception) {
throw LoadDatabaseException(e)
Log.e(TAG, "Unable to reload the database")
if (e is DatabaseException)
throw e
throw DatabaseInputException(e)
} finally {
dataModifiedSinceLastLoading = false
}
}
@Throws(Exception::class)
private fun readDatabaseStream(contentResolver: ContentResolver,
databaseUri: Uri,
openDatabaseKDB: (InputStream) -> Unit,
openDatabaseKDBX: (InputStream) -> Unit) {
try {
// Load Data, pass Uris as InputStreams
val databaseStream = UriUtil.getUriInputStream(contentResolver, databaseUri)
?: throw UnknownDatabaseLocationException()
BufferedInputStream(databaseStream).use { databaseInputStream ->
// We'll end up reading 8 bytes to identify the header. Might as well use two extra.
databaseInputStream.mark(10)
// Get the file directory to save the attachments
val sig1 = databaseInputStream.readBytes4ToUInt()
val sig2 = databaseInputStream.readBytes4ToUInt()
// Return to the start
databaseInputStream.reset()
when {
// Header of database KDB
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> openDatabaseKDB(
databaseInputStream
)
// Header of database KDBX
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> openDatabaseKDBX(
databaseInputStream
)
// Header not recognized
else -> throw SignatureDatabaseException()
}
}
} catch (fileNotFoundException : FileNotFoundException) {
throw FileNotFoundDatabaseException()
}
}
@Throws(DatabaseOutputException::class)
fun saveData(contentResolver: ContentResolver,
cacheDir: File,
databaseCopyUri: Uri?,
mainCredential: MainCredential?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray) {
val saveUri = databaseCopyUri ?: this.fileUri
// Build temp database file to avoid file corruption if error
val cacheFile = File(cacheDir, saveUri.hashCode().toString())
try {
if (saveUri != null) {
// Save in a temp memory to avoid exception
cacheFile.outputStream().use { outputStream ->
mDatabaseKDB?.let { databaseKDB ->
DatabaseOutputKDB(databaseKDB).apply {
writeDatabase(outputStream) {
if (mainCredential != null) {
databaseKDB.deriveMasterKey(
contentResolver,
mainCredential
)
} else {
// No master key change
}
}
}
}
?: mDatabaseKDBX?.let { databaseKDBX ->
DatabaseOutputKDBX(databaseKDBX).apply {
writeDatabase(outputStream) {
if (mainCredential != null) {
// Build new master key from MainCredential
databaseKDBX.deriveMasterKey(
contentResolver,
mainCredential,
challengeResponseRetriever
)
} else {
// Reuse composite key parts
databaseKDBX.deriveCompositeKey(
challengeResponseRetriever
)
}
}
}
}
}
// Copy from the cache to the final stream
UriUtil.getUriOutputStream(contentResolver, saveUri)?.use { outputStream ->
cacheFile.inputStream().use { inputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
}
}
} else {
throw UnknownDatabaseLocationException()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to save database", e)
if (e is DatabaseException)
throw e
throw DatabaseOutputException(e)
} finally {
try {
Log.d(TAG, "Delete database cache file $cacheFile")
cacheFile.delete()
} catch (e: Exception) {
Log.e(TAG, "Cache file $cacheFile cannot be deleted", e)
}
if (databaseCopyUri == null) {
this.dataModifiedSinceLastLoading = false
}
}
}
fun groupIsInRecycleBin(group: Group): Boolean {
val groupKDB = group.groupKDB
val groupKDBX = group.groupKDBX
@@ -845,13 +916,13 @@ class Database {
fun createVirtualGroupFromSearch(searchParameters: SearchParameters,
fromGroup: NodeId<*>? = null,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
return mSearchHelper.createVirtualGroupWithSearchResult(this,
searchParameters, fromGroup, max)
}
fun createVirtualGroupFromSearchInfo(searchInfoString: String,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
return mSearchHelper.createVirtualGroupWithSearchResult(this,
SearchParameters().apply {
searchQuery = searchInfoString
searchInTitles = true
@@ -908,69 +979,6 @@ class Database {
dataModifiedSinceLastLoading = true
}
@Throws(DatabaseOutputException::class)
fun saveData(databaseCopyUri: Uri?, contentResolver: ContentResolver) {
try {
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
try {
fileOutputStream = FileOutputStream(tempFile)
val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) }
pmo?.output()
} catch (e: Exception) {
throw IOException(e)
} finally {
fileOutputStream?.close()
}
// Force data to disk before continuing
try {
fileOutputStream?.fd?.sync()
} catch (e: SyncFailedException) {
// Ignore if fsync fails. We tried.
}
if (!tempFile.renameTo(File(filename))) {
throw IOException()
}
}
} else {
var outputStream: OutputStream? = null
try {
outputStream = contentResolver.openOutputStream(saveUri, "rwt")
outputStream?.let { definedOutputStream ->
val databaseOutput =
mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let {
DatabaseOutputKDBX(
it,
definedOutputStream
)
}
databaseOutput?.output()
}
} catch (e: Exception) {
throw IOException(e)
} finally {
outputStream?.close()
}
}
if (databaseCopyUri == null) {
this.dataModifiedSinceLastLoading = false
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to save database", e)
throw DatabaseOutputException(e)
}
}
fun clearIndexesAndBinaries(filesDirectory: File? = null) {
this.mDatabaseKDB?.clearIndexes()
this.mDatabaseKDBX?.clearIndexes()
@@ -1016,20 +1024,13 @@ class Database {
}
fun validatePasswordEncoding(mainCredential: MainCredential): Boolean {
val password = mainCredential.masterPassword
val password = mainCredential.password
val containsKeyFile = mainCredential.keyFileUri != null
return mDatabaseKDB?.validatePasswordEncoding(password, containsKeyFile)
?: mDatabaseKDBX?.validatePasswordEncoding(password, containsKeyFile)
?: false
}
@Throws(IOException::class)
fun assignMasterKey(key: String?, keyInputStream: InputStream?) {
mDatabaseKDB?.retrieveMasterKey(key, keyInputStream)
mDatabaseKDBX?.retrieveMasterKey(key, keyInputStream)
mDatabaseKDBX?.keyLastChanged = DateInstant()
}
fun rootCanContainsEntry(): Boolean {
return mDatabaseKDB?.rootCanContainsEntry() ?: mDatabaseKDBX?.rootCanContainsEntry() ?: false
}

View File

@@ -0,0 +1,276 @@
/*
* 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.database.element
import android.content.ContentResolver
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import android.util.Base64
import android.util.Log
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
import org.apache.commons.codec.binary.Hex
import org.w3c.dom.Node
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
data class MainCredential(var password: String? = null,
var keyFileUri: Uri? = null,
var hardwareKey: HardwareKey? = null): Parcelable {
constructor(parcel: Parcel) : this() {
password = parcel.readString()
keyFileUri = parcel.readParcelable(Uri::class.java.classLoader)
hardwareKey = parcel.readEnum<HardwareKey>()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(password)
parcel.writeParcelable(keyFileUri, flags)
parcel.writeEnum(hardwareKey)
}
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MainCredential
if (password != other.password) return false
if (keyFileUri != other.keyFileUri) return false
if (hardwareKey != other.hardwareKey) return false
return true
}
override fun hashCode(): Int {
var result = password?.hashCode() ?: 0
result = 31 * result + (keyFileUri?.hashCode() ?: 0)
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<MainCredential> {
override fun createFromParcel(parcel: Parcel): MainCredential {
return MainCredential(parcel)
}
override fun newArray(size: Int): Array<MainCredential?> {
return arrayOfNulls(size)
}
private val TAG = MainCredential::class.java.simpleName
@Throws(IOException::class)
fun retrievePasswordKey(key: String,
encoding: Charset
): ByteArray {
val bKey: ByteArray = try {
key.toByteArray(encoding)
} catch (e: UnsupportedEncodingException) {
key.toByteArray()
}
return HashManager.hashSha256(bKey)
}
@Throws(IOException::class)
fun retrieveFileKey(contentResolver: ContentResolver,
keyFileUri: Uri?,
allowXML: Boolean): ByteArray {
if (keyFileUri == null)
throw IOException("Keyfile URI is null")
val keyData = getKeyFileData(contentResolver, keyFileUri)
?: throw IOException("No data retrieved")
try {
// Check XML key file
val xmlKeyByteArray = if (allowXML)
loadXmlKeyFile(ByteArrayInputStream(keyData))
else
null
if (xmlKeyByteArray != null) {
return xmlKeyByteArray
}
// Check 32 bytes key file
when (keyData.size) {
32 -> return keyData
64 -> try {
return Hex.decodeHex(String(keyData).toCharArray())
} catch (ignoredException: Exception) {
// Key is not base 64, treat it as binary data
}
}
// Hash file as binary data
return HashManager.hashSha256(keyData)
} catch (e: Exception) {
throw IOException("Unable to load the keyfile.", e)
}
}
@Throws(IOException::class)
fun retrieveHardwareKey(keyData: ByteArray): ByteArray {
return HashManager.hashSha256(keyData)
}
@Throws(Exception::class)
private fun getKeyFileData(contentResolver: ContentResolver,
keyFileUri: Uri): ByteArray? {
UriUtil.getUriInputStream(contentResolver, keyFileUri)?.use { keyFileInputStream ->
return keyFileInputStream.readBytes()
}
return null
}
private fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
try {
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
// Disable certain unsecure XML-Parsing DocumentBuilderFactory features
try {
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
} catch (e : ParserConfigurationException) {
Log.w(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)")
}
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
val doc = documentBuilder.parse(keyInputStream)
var xmlKeyFileVersion = 1F
val docElement = doc.documentElement
val keyFileChildNodes = docElement.childNodes
// <KeyFile> Root node
if (docElement == null
|| !docElement.nodeName.equals(XML_NODE_ROOT_NAME, ignoreCase = true)) {
return null
}
if (keyFileChildNodes.length < 2)
return null
for (keyFileChildPosition in 0 until keyFileChildNodes.length) {
val keyFileChildNode = keyFileChildNodes.item(keyFileChildPosition)
// <Meta>
if (keyFileChildNode.nodeName.equals(XML_NODE_META_NAME, ignoreCase = true)) {
val metaChildNodes = keyFileChildNode.childNodes
for (metaChildPosition in 0 until metaChildNodes.length) {
val metaChildNode = metaChildNodes.item(metaChildPosition)
// <Version>
if (metaChildNode.nodeName.equals(XML_NODE_VERSION_NAME, ignoreCase = true)) {
val versionChildNodes = metaChildNode.childNodes
for (versionChildPosition in 0 until versionChildNodes.length) {
val versionChildNode = versionChildNodes.item(versionChildPosition)
if (versionChildNode.nodeType == Node.TEXT_NODE) {
val versionText = versionChildNode.textContent.removeSpaceChars()
try {
xmlKeyFileVersion = versionText.toFloat()
Log.i(TAG, "Reading XML KeyFile version : $xmlKeyFileVersion")
} catch (e: Exception) {
Log.e(TAG, "XML Keyfile version cannot be read : $versionText")
}
}
}
}
}
}
// <Key>
if (keyFileChildNode.nodeName.equals(XML_NODE_KEY_NAME, ignoreCase = true)) {
val keyChildNodes = keyFileChildNode.childNodes
for (keyChildPosition in 0 until keyChildNodes.length) {
val keyChildNode = keyChildNodes.item(keyChildPosition)
// <Data>
if (keyChildNode.nodeName.equals(XML_NODE_DATA_NAME, ignoreCase = true)) {
var hashString : String? = null
if (keyChildNode.hasAttributes()) {
val dataNodeAttributes = keyChildNode.attributes
hashString = dataNodeAttributes
.getNamedItem(XML_ATTRIBUTE_DATA_HASH).nodeValue
}
val dataChildNodes = keyChildNode.childNodes
for (dataChildPosition in 0 until dataChildNodes.length) {
val dataChildNode = dataChildNodes.item(dataChildPosition)
if (dataChildNode.nodeType == Node.TEXT_NODE) {
val dataString = dataChildNode.textContent.removeSpaceChars()
when (xmlKeyFileVersion) {
1F -> {
// No hash in KeyFile XML version 1
return Base64.decode(dataString,
DatabaseKDBX.BASE_64_FLAG
)
}
2F -> {
return if (hashString != null
&& checkKeyFileHash(dataString, hashString)
) {
Log.i(TAG, "Successful key file hash check.")
Hex.decodeHex(dataString.toCharArray())
} else {
Log.e(TAG, "Unable to check the hash of the key file.")
null
}
}
}
}
}
}
}
}
}
} catch (e: Exception) {
return null
}
return null
}
private fun checkKeyFileHash(data: String, hash: String): Boolean {
var success = false
try {
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
val dataDigest = HashManager.hashSha256(Hex.decodeHex(data.toCharArray()))
.copyOfRange(0, 4).toHexString()
success = dataDigest == hash
} catch (e: Exception) {
e.printStackTrace()
}
return success
}
private const val XML_NODE_ROOT_NAME = "KeyFile"
private const val XML_NODE_META_NAME = "Meta"
private const val XML_NODE_VERSION_NAME = "Version"
private const val XML_NODE_KEY_NAME = "Key"
private const val XML_NODE_DATA_NAME = "Data"
private const val XML_ATTRIBUTE_DATA_HASH = "Hash"
}
}

View File

@@ -19,11 +19,13 @@
package com.kunzisoft.keepass.database.element.database
import android.content.ContentResolver
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.encrypt.aes.AESTransformer
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
@@ -31,8 +33,10 @@ import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.exception.EmptyKeyDatabaseException
import com.kunzisoft.keepass.database.exception.HardwareKeyDatabaseException
import java.io.IOException
import java.io.InputStream
import java.nio.charset.Charset
import java.util.*
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
@@ -56,8 +60,8 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
KdfFactory.aesKdf
)
override val passwordEncoding: String
get() = "ISO-8859-1"
override val passwordEncoding: Charset
get() = Charsets.ISO_8859_1
override var numberKeyEncryptionRounds = 300L
@@ -116,20 +120,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return newId
}
@Throws(IOException::class)
override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray {
return if (key != null && keyInputStream != null) {
getCompositeKey(key, keyInputStream)
} else if (key != null) { // key.length() >= 0
getPasswordKey(key)
} else if (keyInputStream != null) { // key == null
getFileKey(keyInputStream)
} else {
throw IllegalArgumentException("Key cannot be empty.")
}
}
@Throws(IOException::class)
fun makeFinalKey(masterSeed: ByteArray, transformSeed: ByteArray, numRounds: Long) {
// Encrypt the master key a few times to make brute-force key-search harder
@@ -138,6 +128,41 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
finalKey = HashManager.hashSha256(masterSeed, transformedKey)
}
fun deriveMasterKey(
contentResolver: ContentResolver,
mainCredential: MainCredential
) {
// Exception when no password
if (mainCredential.hardwareKey != null)
throw HardwareKeyDatabaseException()
if (mainCredential.password == null && mainCredential.keyFileUri == null)
throw EmptyKeyDatabaseException()
// Retrieve plain data
val password = mainCredential.password
val keyFileUri = mainCredential.keyFileUri
val passwordBytes = if (password != null) MainCredential.retrievePasswordKey(
password,
passwordEncoding
) else null
val keyFileBytes = if (keyFileUri != null) MainCredential.retrieveFileKey(
contentResolver,
keyFileUri,
false
) else null
// Build master key
if (passwordBytes != null
&& keyFileBytes != null) {
this.masterKey = HashManager.hashSha256(
passwordBytes,
keyFileBytes
)
} else {
this.masterKey = passwordBytes ?: keyFileBytes ?: byteArrayOf(0)
}
}
override fun createGroup(): GroupKDB {
return GroupKDB()
}

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