Compare commits

...

169 Commits

Author SHA1 Message Date
J-Jamet
88ac2ecf98 Merge branch 'release/2.5.0.0beta18' 2018-11-01 17:45:56 +01:00
J-Jamet
227cb078a5 Fix theme warning 2018-11-01 17:37:37 +01:00
J-Jamet
a17582bf6d Merge branch 'translations' into develop 2018-11-01 17:25:25 +01:00
J-Jamet
e8ba5dc6e2 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2018-11-01 17:20:33 +01:00
J-Jamet
f37d3324e6 Upgrade app to version 18 2018-10-27 21:57:32 +02:00
J-Jamet
fcb06c8c22 Upgrade changelog 2018-10-27 21:42:00 +02:00
J-Jamet
d8bdf4500a Merge branch 'feature/Attachments' into develop 2018-10-27 21:38:57 +02:00
J-Jamet
69ea4d5414 Fix views 2018-10-27 20:46:17 +02:00
J-Jamet
7e9f99e5c8 Change the default icon pack 2018-10-27 20:30:31 +02:00
J-Jamet
95541d99c4 Replace a password sentence 2018-10-27 20:30:08 +02:00
J-Jamet
05ca7e6d69 Merge branch 'comradekingu-patch-2' into develop 2018-10-27 20:13:53 +02:00
J-Jamet
644a4a4886 Update strings 2018-10-27 20:13:15 +02:00
J-Jamet
7eb82ddbea Merge branch 'patch-2' of git://github.com/comradekingu/KeePassDX into comradekingu-patch-2 with corrections 2018-10-27 19:42:42 +02:00
J-Jamet
e721d4ca9e Fix keyboard clear and optimize views 2018-10-27 16:59:16 +02:00
J-Jamet
c72582b679 Update file selection view, title and Changelog 2018-10-27 16:30:36 +02:00
J-Jamet
217f0a82b5 Merge branch 'feature/Magikeyboard' into develop 2018-10-27 14:25:41 +02:00
J-Jamet
1efd172b42 Fix image 2018-10-27 14:17:59 +02:00
J-Jamet
e8d0d03ad9 Add keyboard Timeout 2018-10-27 13:56:08 +02:00
J-Jamet
6cb97de793 Remove keyboard sound and vibration duration settings 2018-10-20 12:57:26 +02:00
J-Jamet
77dbdc235a Add alpha channel to capture_keyboard images 2018-10-20 12:48:44 +02:00
J-Jamet
a9c87ea2b8 Add custom fields selection in keyboard 2018-10-13 16:10:45 +02:00
J-Jamet
232829526c Upgrade grdale 2018-10-13 10:52:04 +02:00
אלקנה בירדוגו
b5d8b2f14c Translated using Weblate (Hebrew)
Currently translated at 33.7% (110 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/he/
2018-09-27 15:31:03 +02:00
C. Rüdinger
c2673a627c Translated using Weblate (German)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-09-24 15:27:48 +02:00
Tatiana Canales
bb49256188 Translated using Weblate (Spanish)
Currently translated at 66.5% (217 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2018-09-22 11:36:53 +02:00
Allan Nordhøy
1b6a284dd6 "Database name" 2018-09-21 02:42:26 +02:00
Allan Nordhøy
36b35b907e Requested changes implemented 2018-09-21 02:41:40 +02:00
Allan Nordhøy
f61b310ff2 Language rework 2
Transformation rounds
2018-09-19 03:39:04 +02:00
J-Jamet
c1e84b3bf1 Add popup view for custom fields 2018-09-18 21:27:36 +02:00
Pavel Borecki
9971d27ef3 Translated using Weblate (Czech)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2018-09-18 20:22:34 +02:00
J-Jamet
3c0835f725 Fix keyboard settings 2018-09-18 15:52:38 +02:00
J-Jamet
023aba2b60 Remove Magikeyboard and Model modules for easier integration 2018-09-18 11:46:37 +02:00
J-Jamet
abc0a14dda Update gradle 2018-09-18 10:14:15 +02:00
J-Jamet
dc23c6a445 Optimize memory pool and revert algo 2018-09-18 10:11:44 +02:00
J-Jamet
161a0ffc59 Change links 2018-09-17 22:38:48 +02:00
J-Jamet
b96436c906 Change small code 2018-09-16 11:40:22 +02:00
J-Jamet
cee7419208 Fix equals and hashcode for ProtectedBinary 2018-09-15 17:36:14 +02:00
J-Jamet
9ff6e7a080 Clear the cache to keep a small memory when the app is down 2018-09-15 17:22:42 +02:00
Lieven Blancke
9112a568d9 Translated using Weblate (Dutch)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2018-09-09 15:16:12 +02:00
Heimen Stoffels
8d0c9bc894 Translated using Weblate (Dutch)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2018-09-08 12:16:14 +02:00
RainSlide
e7b4e4501e Translated using Weblate (Chinese (Simplified))
Currently translated at 63.8% (208 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2018-09-08 12:14:45 +02:00
J-Jamet
df80084822 Fix attachment recovery 2018-09-06 22:05:31 +02:00
Heimen Stoffels
4df2891b1a Translated using Weblate (Dutch)
Currently translated at 46.9% (153 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2018-09-06 13:15:42 +02:00
J-Jamet
d0560677fa Temp commit 2018-09-05 22:02:24 +02:00
J-Jamet
9130a3851f Fix Out of memory for large attachment #115 2018-09-03 20:06:50 +02:00
J-Jamet
bb3fb26847 Update gradle 2018-09-03 12:00:57 +02:00
BO41
8406264138 Translated using Weblate (German)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-29 15:30:49 +02:00
ButterflyOfFire
e51f48ebe0 Translated using Weblate (Arabic)
Currently translated at 41.4% (135 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2018-08-29 10:34:27 +02:00
Daniel
a0a2aa4c5d Translated using Weblate (German)
Currently translated at 99.3% (324 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-24 12:38:48 +02:00
Claus Rüdinger
65cea7b5ec Translated using Weblate (German)
Currently translated at 99.3% (324 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-24 12:38:48 +02:00
WaldiS
fb589419de Translated using Weblate (Polish)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2018-08-23 18:50:11 +02:00
Allan Nordhøy
ae9844d5a1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 90.1% (294 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2018-08-23 08:41:23 +02:00
J-Jamet
645ea21581 Remove warning_read_only 2018-08-23 00:24:18 +02:00
Allan Nordhøy
60fd675dff Translated using Weblate (Norwegian Bokmål)
Currently translated at 79.4% (259 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2018-08-22 05:41:35 +02:00
Allan Nordhøy
48903da446 Translated using Weblate (Danish)
Currently translated at 99.6% (325 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2018-08-22 04:36:29 +02:00
Claus Rüdinger
5399df134c Translated using Weblate (German)
Currently translated at 97.5% (318 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-21 21:37:54 +02:00
WaldiS
a858040ace Translated using Weblate (Polish)
Currently translated at 73.9% (241 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2018-08-21 20:41:36 +02:00
Allan Nordhøy
d792b7d5ae Added translation using Weblate (Norwegian Bokmål) 2018-08-21 03:27:23 +02:00
Claus Rüdinger
8b3ed1a7dc Translated using Weblate (German)
Currently translated at 97.5% (318 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-19 01:38:34 +02:00
jan madsen
47004857ea Translated using Weblate (Danish)
Currently translated at 99.6% (325 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2018-08-16 18:35:31 +02:00
J-Jamet
82a6b8d7ef Merge branch 'master' into develop 2018-08-15 09:32:13 +02:00
J-Jamet
a30bcf1fa7 Merge branch 'mohammadnaseri-master' into develop 2018-08-15 09:30:55 +02:00
Hosted Weblate
febe978fa1 Merge branch 'origin/master' into Weblate 2018-08-15 09:28:48 +02:00
jan madsen
1eeefcf495 Translated using Weblate (Danish)
Currently translated at 96.6% (315 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2018-08-15 09:28:48 +02:00
Jérémy JAMET
ff6b0eee6c Merge pull request #167 from tuxayo/patch-1
CHANGELOG: Fix forgotten version bump
2018-08-15 09:28:33 +02:00
Claus Rüdinger
a7c8eaa937 Translated using Weblate (German)
Currently translated at 92.3% (301 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-14 23:37:23 +02:00
Telugu Speaker
55545e31e8 Translated using Weblate (Telugu)
Currently translated at 3.3% (11 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/te/
2018-08-14 04:44:56 +02:00
Tobirium
34132c6da7 Translated using Weblate (German)
Currently translated at 91,4% (298 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-13 22:53:31 +02:00
Claus Rüdinger
fbafa99a3b Translated using Weblate (German)
Currently translated at 91,4% (298 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-13 22:53:31 +02:00
random r
bd63805611 Translated using Weblate (Italian)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-08-13 11:39:07 +02:00
lnxw
ae8398a434 Added translation using Weblate (Telugu) 2018-08-13 03:30:16 +02:00
عادل حسين المطهر
0b9318de88 Translated using Weblate (Arabic)
Currently translated at 37.4% (122 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2018-08-12 16:37:49 +02:00
WaldiS
55e4bbf9db Translated using Weblate (Polish)
Currently translated at 53.3% (174 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2018-08-11 19:42:59 +02:00
tuxayo
f33a572751 CHANGELOG: Fix forgotten version bump 2018-08-11 14:22:06 +02:00
K
2849cedbd8 Translated using Weblate (Latvian)
Currently translated at 34.6% (113 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lv/
2018-08-11 09:40:28 +02:00
Mohammad Naseri
041d1b29d1 Passwords should be invisible to the accessibility services 2018-08-10 15:43:49 +02:00
K
cdcdc2fee1 Translated using Weblate (Latvian)
Currently translated at 34.0% (111 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lv/
2018-08-09 18:39:53 +02:00
Tobirium
8e35d5bc8d Translated using Weblate (German)
Currently translated at 90.4% (295 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-08 18:38:37 +02:00
Daniel
f12e368707 Translated using Weblate (German)
Currently translated at 90.4% (295 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-08-08 18:38:36 +02:00
J-Jamet
f01895c7a7 Remove build version from app version (commit TAG = app name) 2018-08-08 12:35:15 +02:00
Aidar Gilmutdinov
4ce0c067e1 Translated using Weblate (Russian)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2018-08-07 22:41:35 +02:00
WaldiS
728a2cbfae Translated using Weblate (Polish)
Currently translated at 50.9% (166 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2018-08-07 17:41:48 +02:00
Tiago A. Reul
a4aab3b933 Translated using Weblate (Portuguese (Brazil))
Currently translated at 73.6% (240 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2018-08-07 16:43:32 +02:00
Aidar Gilmutdinov
11ee0d6544 Translated using Weblate (Russian)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2018-08-06 21:52:24 +02:00
Allan Nordhøy
2b80dad47c Translated using Weblate (Norwegian Bokmål)
Currently translated at 2.1% (7 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb/
2018-08-06 19:41:56 +02:00
random r
7bb13ec1c2 Translated using Weblate (Italian)
Currently translated at 100,0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-08-06 11:19:27 +02:00
Ty Moss
344010d2bb Translated using Weblate (Norwegian Bokmål)
Currently translated at 2,1% (7 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb/
2018-08-05 18:42:40 +02:00
Ty Moss
ef37ecddc6 Added translation using Weblate (Norwegian Bokmål) 2018-08-05 18:05:54 +02:00
Kunzisoft
91a38e205e Translated using Weblate (French)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-08-04 11:38:13 +02:00
Kunzisoft
bd4de382e4 Translated using Weblate (English)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2018-08-04 10:36:42 +02:00
random r
623244c3bd Translated using Weblate (Italian)
Currently translated at 99.6% (325 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-08-03 14:39:19 +02:00
Kunzisoft
7a06ff6dec Translated using Weblate (French)
Currently translated at 100.0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-08-03 11:23:15 +02:00
random r
6d735e24ff Translated using Weblate (Italian)
Currently translated at 100,0% (326 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-08-02 14:23:10 +02:00
Hosted Weblate
5c30d544fa Merge branch 'origin/master' into Weblate 2018-08-02 13:11:19 +02:00
random r
54a3876288 Translated using Weblate (Italian)
Currently translated at 88.0% (287 of 326 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-08-02 13:11:19 +02:00
J-Jamet
57f71afb98 Fix font and upgrade version 2018-08-02 13:02:18 +02:00
J-Jamet
d8ec198f6f Fix null pointer 2018-08-02 11:58:15 +02:00
Hosted Weblate
faa6d7c9f6 Merge branch 'origin/master' into Weblate 2018-08-02 11:21:06 +02:00
Kunzisoft
d79a5b17a5 Translated using Weblate (French)
Currently translated at 100.0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-08-02 11:21:05 +02:00
J-Jamet
34deea5d32 Merge tag '2.5.0.0beta16' into develop
2.5.0.0bet16
2018-08-02 11:19:58 +02:00
J-Jamet
edc7324c1d Merge branch 'release/2.5.0.0beta16' 2018-08-02 11:19:41 +02:00
J-Jamet
b9190e8254 Fix translations 2018-08-02 11:18:59 +02:00
J-Jamet
534878ae99 Merge branch 'translations' into develop 2018-08-02 11:04:39 +02:00
J-Jamet
99bfd21ecc Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2018-08-02 11:03:37 +02:00
J-Jamet
cb5c324c9d Fix search / Fix icon - back #138 / Upgrade changelogs 2018-08-02 10:34:04 +02:00
J-Jamet
55dc504f26 Fix visual elements 2018-08-01 15:45:49 +02:00
J-Jamet
ecb0138c90 Add display username settings and rearrange form filling 2018-08-01 12:00:15 +02:00
J-Jamet
0860eeb87f Show website domains if entries given no title #124 and add username 2018-07-31 22:04:41 +02:00
J-Jamet
f08bff61cf Update changelogs 2018-07-31 08:52:58 +02:00
J-Jamet
432aca6465 Fix transparent toolbar 2018-07-30 22:17:23 +02:00
J-Jamet
7cdb8db146 New error message #154 2018-07-30 21:10:11 +02:00
random r
6ba9dedcb8 Translated using Weblate (Italian)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-07-30 11:59:13 +02:00
random r
6d603608f4 Translated using Weblate (Italian)
Currently translated at 99.3% (319 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-07-30 11:58:11 +02:00
Kunzisoft
64f4d9fb84 Translated using Weblate (French)
Currently translated at 99.6% (320 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-07-29 14:37:40 +02:00
J-Jamet
60ccc450ae Fix read-only education screen 2018-07-29 09:44:01 +02:00
J-Jamet
ddb5f327a3 Add warning dialog on password copy button and sort settings 2018-07-28 18:00:57 +02:00
J-Jamet
df90ea42eb Deleting points in parameter explanations 2018-07-28 16:18:36 +02:00
Jérémy JAMET
a062d648b3 Merge pull request #158 from comradekingu/patch-1
Spelling: Language rework
2018-07-28 15:56:37 +02:00
J-Jamet
a59ae820b5 Add gray copy icon when copy not available 2018-07-28 15:18:34 +02:00
J-Jamet
228831acdd Upgrade version and add changelogs 2018-07-28 14:29:03 +02:00
J-Jamet
bf15ee43da Populate custom fields in search entry 2018-07-28 14:00:49 +02:00
J-Jamet
608f45677c Fix quick search with magikeyboard 2018-07-28 09:37:26 +02:00
Daniel
1cb15f214b Translated using Weblate (German)
Currently translated at 100.0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-28 00:37:41 +02:00
Claus Rüdinger
cd4a9e9b03 Translated using Weblate (German)
Currently translated at 100.0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-28 00:37:40 +02:00
J-Jamet
d6eae56d4f Merge branch 'feature/Search' into develop #26 #147 #153 2018-07-27 22:19:43 +02:00
J-Jamet
b5a87a63dc Fix search fragment 2018-07-27 21:39:26 +02:00
Allan Nordhøy
1eb17c4f34 Language rework 2018-07-27 15:11:56 +02:00
Hadrián Candela
8a6ce1f711 Translated using Weblate (Galician)
Currently translated at 7.4% (24 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2018-07-27 13:38:02 +02:00
J-Jamet
0f22f8af45 Add search title 2018-07-27 10:54:58 +02:00
Tobirium
b2e81e6fd9 Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-26 23:52:55 +02:00
Daniel
f82eab942d Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-26 23:52:55 +02:00
J-Jamet
aa29aec40f Add search suggestions 2018-07-26 14:08:48 +02:00
Hadrián Candela
181def52ab Added translation using Weblate (Galician) 2018-07-26 13:01:37 +02:00
HybridGlucose
96d2bd63cc Translated using Weblate (Chinese (Traditional))
Currently translated at 44.5% (143 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2018-07-25 20:36:13 +02:00
Claus Rüdinger
194021a957 Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-25 18:38:46 +02:00
Daniel
9e307f94ea Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-25 18:38:45 +02:00
Tobirium
155b2de138 Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-25 18:38:45 +02:00
J-Jamet
c76c3fd2be Fix fragment duplication 2018-07-25 11:50:18 +02:00
Matheus Gritz
01c5554944 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2018-07-24 19:11:13 +02:00
J-Jamet
542cf65b41 Fix timeout and new animation 2018-07-24 18:32:19 +02:00
J-Jamet
f037561a67 Search in fragment 2018-07-24 18:02:08 +02:00
J-Jamet
379263a6d3 Search and Group in one activity 2018-07-24 14:48:03 +02:00
J-Jamet
c420ca01f6 Update Readme 2018-07-23 14:21:46 +02:00
J-Jamet
a1f9db6eee Try to solve the date crash 2018-07-23 14:12:07 +02:00
J-Jamet
c9212174c4 Change text by reset 2018-07-22 12:13:02 +02:00
J-Jamet
0065336377 Merge tag '2.5.0.0beta15' into develop
2.5.0.0beta15
2018-07-19 11:27:47 +02:00
J-Jamet
4f9625a3e1 Merge branch 'release/2.5.0.0beta15' 2018-07-19 11:27:18 +02:00
J-Jamet
7688ebd29b Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2018-07-19 11:15:53 +02:00
Claus Rüdinger
3f2a7f1eb3 Translated using Weblate (German)
Currently translated at 99.3% (313 of 315 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-19 11:08:28 +02:00
Veronica Small
b3d067d0c8 Translated using Weblate (Chinese (Simplified))
Currently translated at 41.9% (132 of 315 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2018-07-19 11:08:28 +02:00
Kunzisoft
a3b4ad5ac1 Translated using Weblate (French)
Currently translated at 100.0% (315 of 315 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-07-19 11:08:28 +02:00
J-Jamet
e3b329d27f Update version and changelog 2018-07-19 11:01:32 +02:00
J-Jamet
7d10c43822 Fix orientation change 2018-07-19 10:40:36 +02:00
J-Jamet
fcb0d45d39 Fix Magikeyboard string 2018-07-19 08:58:20 +02:00
J-Jamet
5492db0223 Merge branch 'feature/ReadOnlyMode' into develop 2018-07-18 18:51:51 +02:00
J-Jamet
2207b05f5f Fix PwDate 2018-07-18 18:12:43 +02:00
J-Jamet
f15a0c2591 Optimize memory 2018-07-18 17:56:32 +02:00
J-Jamet
92fb22129c #77 Add read-only mode to the floating menu 2018-07-18 16:29:18 +02:00
J-Jamet
ccca9c4400 #77 Add read-only mode 2018-07-18 14:37:39 +02:00
J-Jamet
0597cb4416 #148 fix second element to copy 2018-07-16 22:02:24 +02:00
J-Jamet
0602174e50 Merge tag '2.5.0.0beta14' into develop
2.5.0.0beta14
2018-07-15 10:59:01 +02:00
J-Jamet
6fddc92ce7 Merge branch 'release/2.5.0.0beta14' 2018-07-15 10:58:48 +02:00
J-Jamet
aa30df6454 Update version / changelogs 2018-07-15 10:49:45 +02:00
J-Jamet
f6c61ab407 Show error message when sort can't be done 2018-07-15 10:33:54 +02:00
J-Jamet
2ab81ed77c Add parcelables 2018-07-15 10:24:30 +02:00
J-Jamet
0ade035f43 Catch construct element exception 2018-07-14 18:11:20 +02:00
J-Jamet
73b62035d8 Fix MagikIME nullpointer 2018-07-14 16:07:38 +02:00
J-Jamet
28837db308 Fix search and search for keyboard 2018-07-14 14:42:51 +02:00
J-Jamet
85befef260 Merge tag '2.5.0.0beta13' into develop
2.5.0.0beta13
2018-07-14 11:11:49 +02:00
229 changed files with 7259 additions and 3573 deletions

View File

@@ -1,4 +1,34 @@
KeepassDX (2.5.0.0beta11) KeepassDX (2.5.0.0beta18)
* New recent databases views
* New information dialog
* Custom fields for the Magikeyboard
* Timeout for the Magikeyboard
* Long press for keyboard selection
* Fix memory when opening the database
* Memory management for attachments
KeepassDX (2.5.0.0beta17)
* Fix font and search
KeepassDX (2.5.0.0beta16)
* New search in a single fragment
* Search suggestions
* Added the display of usernames
* Added translations
* Fix read-only mode
* Fix parcelable / toolbar / back
KeepassDX (2.5.0.0beta15)
* Read only mode
* Best group recovery for the navigation fragment
* Fix copies in notifications
* Fix orientation
* Added translations
KeepassDX (2.5.0.0beta14)
* Optimize all the memory with parcelables / fix search
KeepassDX (2.5.0.0beta13)
* Fix memory issue with parcelable (crash in beta12 version) * Fix memory issue with parcelable (crash in beta12 version)
KeepassDX (2.5.0.0beta12) KeepassDX (2.5.0.0beta12)

55
FAQ.md
View File

@@ -1,55 +0,0 @@
# F.A.Q.
## Why KeePass DX?
KeePass DX is an **Android password manager** implemented from Keepass password manager.
KeePass DX was created to meet the security and usability needs of a KeePass application on Android :
- To be easy to use with **secure password management and form filling tools**.
- To use only tools under **open source license** to guarantee the security of the application (With [open source store](https://f-droid.org/en/) and no closed API).
- To be in a **native langage** (java) for weight, security and a better integration of the application.
- To respect **Android design, architecture and ergonomic**.
## What makes KeePass DX stand out from other password managers?
- We **do not recover your sensitive data** on a private server or a closed cloud, you have control of your passwords.
- We respect **KeePass file standards** to maintain compatibility and data porting on different devices (computers and portable devices with different operating system).
- The code is **open source**, which implies increased **security**, you can check how the encryption algorithms are implemented.
- We remain attentive to **your needs** and we can even integrate the features that you have defined.
- We **do not put advertising** even in the free version.
## How am I sure my passwords are safely stored on the application?
- We allow users to save and use passwords, keys and digital identities in a secure way by **integrating the last encryption algorithms** and **Android architecture standards**.
- You can increase the security of your database by increasing the rounds of encryption keys. *(In Settings -> Database Settings when your database is open)* **Warning**: *Increase the number of rounds sparingly to have a reasonable opening time.*
## Can I store my data on a cloud storage?
**Yes** this is possible. Otherwise, we **recommend using cloud with personal server and open source license**, like [NextCloud](https://f-droid.org/en/packages/com.nextcloud.client/) to be sure how your databases are stored.
## Can I recover my passwords on another device if I loose my main device?
**Yes** you can, but you **must first save the .kdb or .kdbx file from your database to an external storage** *(like a hardrive or a cloud)*.
We recommend you save your data after each modification so incase you loose your android device you could retrieve the data and import it into the new KeePass DX installed on the new android device.
## Why are updates not available at the same time on all stores?
- **PlayStore** only needs an APK generated and manually signed to be available on the store, it usually takes **20 minutes** to be available because it is deployed with fastlane. But the management of the APK and its data by the google servers is obscure.
- **F-Droid**, to **ensure that the code is open source**, checks the sources directly on git repository (by checking the presence of new tags) and builds itself the APK that the server signs during the compilation of code and dependencies. Updating the project will take **1-10 days** for F-Droid to analyze all available repositories, build sources and deploy the generated APK. So F-Droid is slower for deployment but it is run by **volunteers** and guaranteed a **clean APK**. :)
## Why not an online version?
The offline and online client concepts only exists with Keepass2Android because the file access network tools are directly integrated into the code of the main application. Which is a very dubious choice knowing that **it is not normally the purpose of a password management application to take care of external file synchronization on clouds** (which can be under closed licensed and recover your data base), it is rather the purpose of the [file management application](https://developer.android.com/guide/topics/providers/document-provider).
## Can I open my database easily other than with a password?
**Yes**, we have integrated a secure openning option of fingerprint for android devices that support this feature, so no one can access the application without scanning his/her fingerprint or fill a master key.
## Can I open my database without my master key (master password and/or key file)?
**No**, you can not open a database file without the master password (and / or) the associated key file. Be sure to remember your master password and save the key file in a safe place.
## Can I suggest features and report bugs for the application?
**Yes**, we welcome this you could go ahead and do that on our github:
https://github.com/Kunzisoft/KeePassDX

View File

@@ -28,7 +28,7 @@ KeePass DX is a **free open source password manager for Android**, which helps y
Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly. Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free.* *Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
## Contributions ## Contributions
@@ -53,7 +53,7 @@ You can contribute in different ways to help us on our work.
## F.A.Q. ## F.A.Q.
Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassDX/blob/master/FAQ.md) Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
## Other devices ## Other devices
@@ -67,7 +67,7 @@ Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassD
This file is part of KeePass DX. This file is part of KeePass DX.
KeePass DX is free software: you can redistribute it and/or modify [KeePass DX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.

View File

@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion 27 compileSdkVersion 27
buildToolsVersion '27.0.3' buildToolsVersion '28.0.2'
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 15 minSdkVersion 14
targetSdkVersion 27 targetSdkVersion 27
versionCode = 13 versionCode = 18
versionName = "2.5.0.0beta13" versionName = "2.5.0.0beta18"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"
@@ -39,7 +39,7 @@ android {
productFlavors { productFlavors {
libre { libre {
applicationIdSuffix = ".libre" applicationIdSuffix = ".libre"
versionNameSuffix "-libre" buildConfigField "String", "BUILD_VERSION", "\"libre\""
buildConfigField "boolean", "FULL_VERSION", "true" buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "false" buildConfigField "boolean", "CLOSED_STORE", "false"
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}" buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
@@ -48,7 +48,7 @@ android {
} }
pro { pro {
applicationIdSuffix = ".pro" applicationIdSuffix = ".pro"
versionNameSuffix "-pro" buildConfigField "String", "BUILD_VERSION", "\"pro\""
buildConfigField "boolean", "FULL_VERSION", "true" buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "true" buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", "{}" buildConfigField "String[]", "STYLES_DISABLED", "{}"
@@ -56,7 +56,7 @@ android {
} }
free { free {
applicationIdSuffix = ".free" applicationIdSuffix = ".free"
versionNameSuffix "-free" buildConfigField "String", "BUILD_VERSION", "\"free\""
buildConfigField "boolean", "FULL_VERSION", "false" buildConfigField "boolean", "FULL_VERSION", "false"
buildConfigField "boolean", "CLOSED_STORE", "true" buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}" buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
@@ -94,7 +94,7 @@ dependencies {
implementation 'joda-time:joda-time:2.9.9' implementation 'joda-time:joda-time:2.9.9'
implementation 'org.sufficientlysecure:html-textview:3.5' implementation 'org.sufficientlysecure:html-textview:3.5'
implementation 'com.nononsenseapps:filepicker:4.1.0' implementation 'com.nononsenseapps:filepicker:4.1.0'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.11.0' implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
// Permissions // Permissions
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") { implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
// if you don't use android.app.Fragment you can exclude support for them // if you don't use android.app.Fragment you can exclude support for them
@@ -103,13 +103,14 @@ dependencies {
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion" annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
// Apache Commons Collections // Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1' implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2'
// Base64 // Base64
implementation 'biz.source_code:base64coder:2010-12-19' implementation 'biz.source_code:base64coder:2010-12-19'
// IO-Extras
implementation 'com.github.davidmoten:io-extras:0.1'
implementation 'com.google.code.gson:gson:2.8.4' implementation 'com.google.code.gson:gson:2.8.4'
implementation 'com.google.guava:guava:23.0-android' implementation 'com.google.guava:guava:23.0-android'
// Icon pack, classic for all, material for libre and pro // Icon pack
implementation project(path: ':icon-pack-classic') implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material') implementation project(path: ':icon-pack-material')
implementation project(path: ':magikeyboard')
implementation project(path: ':keepass-model')
} }

View File

@@ -45,10 +45,10 @@ public class PwEntryTestV4 extends TestCase {
entry.setBackgroupColor("blue"); entry.setBackgroupColor("blue");
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1})); entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
entry.setCustomIcon(new PwIconCustom(UUID.randomUUID(), new byte[0])); entry.setIconCustom(new PwIconCustom(UUID.randomUUID(), new byte[0]));
entry.setForegroundColor("red"); entry.setForegroundColor("red");
entry.addToHistory(new PwEntryV4()); entry.addToHistory(new PwEntryV4());
entry.setIcon(new PwIconStandard(5)); entry.setIconStandard(new PwIconStandard(5));
entry.setOverrideURL("override"); entry.setOverrideURL("override");
entry.setParent(new PwGroupV4()); entry.setParent(new PwGroupV4());
entry.addExtraField("key2", new ProtectedString(false, "value2")); entry.addExtraField("key2", new ProtectedString(false, "value2"));

View File

@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3; import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwGroup; import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable; import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
import com.kunzisoft.keepass.search.SearchDbHelper; import com.kunzisoft.keepass.database.search.SearchDbHelper;
import java.util.List; import java.util.List;
@@ -72,8 +72,8 @@ public class DeleteEntry extends AndroidTestCase {
// Verify the entries were removed from the search index // Verify the entries were removed from the search index
SearchDbHelper dbHelp = new SearchDbHelper(ctx); SearchDbHelper dbHelp = new SearchDbHelper(ctx);
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME); PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME); PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries()); assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries()); assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());

View File

@@ -19,11 +19,11 @@
*/ */
package com.kunzisoft.keepass.tests.database; package com.kunzisoft.keepass.tests.database;
import junit.framework.TestCase;
import com.kunzisoft.keepass.database.PwDatabaseV4; import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwEntryV4; import com.kunzisoft.keepass.database.PwEntryV4;
import junit.framework.TestCase;
public class EntryV4 extends TestCase { public class EntryV4 extends TestCase {
public void testBackup() { public void testBackup() {
@@ -46,7 +46,7 @@ public class EntryV4 extends TestCase {
entry.createBackup(db); entry.createBackup(db);
PwEntryV4 backup = entry.getHistory().get(0); PwEntryV4 backup = entry.getHistory().get(0);
entry.endToManageFieldReferences(); entry.stopToManageFieldReferences();
assertEquals("Title2", backup.getTitle()); assertEquals("Title2", backup.getTitle());
assertEquals("User2", backup.getUsername()); assertEquals("User2", backup.getUsername());
} }

View File

@@ -20,8 +20,7 @@
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup" android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:theme="@style/KeepassDXStyle.Night" android:theme="@style/KeepassDXStyle.Night">
tools:replace="android:theme">
<!-- TODO backup API Key --> <!-- TODO backup API Key -->
<meta-data <meta-data
android:name="com.google.android.backup.api_key" android:name="com.google.android.backup.api_key"
@@ -104,11 +103,19 @@
<activity <activity
android:name="com.kunzisoft.keepass.activities.GroupActivity" android:name="com.kunzisoft.keepass.activities.GroupActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan"
android:launchMode="singleTop">
<meta-data <meta-data
android:name="android.app.default_searchable" android:name="android.app.default_searchable"
android:value="com.kunzisoft.keepass.search.SearchResults" android:value="com.kunzisoft.keepass.search.SearchResults"
android:exported="false"/> android:exported="false"/>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity> </activity>
<activity <activity
android:name="com.kunzisoft.keepass.activities.EntryActivity" android:name="com.kunzisoft.keepass.activities.EntryActivity"
@@ -118,24 +125,28 @@
android:name="com.kunzisoft.keepass.activities.EntryEditActivity" android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" /> android:windowSoftInputMode="stateHidden" />
<activity
android:name="com.kunzisoft.keepass.search.SearchResultsActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" /> <activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity" <activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity"
android:configChanges="orientation|keyboardHidden" /> android:configChanges="orientation|keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.selection.EntrySelectionAuthActivity" <activity android:name="com.kunzisoft.keepass.selection.EntrySelectionAuthActivity"
android:configChanges="orientation|keyboardHidden" /> android:configChanges="orientation|keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" /> <activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
<activity android:name="com.kunzisoft.keepass.magikeyboard.EntryRetrieverActivity"
android:label="@string/keyboard_name">
</activity>
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
android:label="@string/keyboard_setting_label">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<!-- Receiver for Keyboard -->
<receiver
android:name="com.kunzisoft.keepass.magikeyboard.receiver.NotificationDeleteBroadcastReceiver"
android:exported="false" >
</receiver>
<service <service
android:name="com.kunzisoft.keepass.notifications.NotificationCopyingService" android:name="com.kunzisoft.keepass.notifications.NotificationCopyingService"
android:enabled="true" android:enabled="true"
@@ -152,6 +163,17 @@
<action android:name="android.service.autofill.AutofillService" /> <action android:name="android.service.autofill.AutofillService" />
</intent-filter> </intent-filter>
</service> </service>
<service
android:name="com.kunzisoft.keepass.magikeyboard.MagikIME"
android:label="@string/keyboard_label"
android:permission="android.permission.BIND_INPUT_METHOD" >
<meta-data android:name="android.view.im"
android:resource="@xml/keyboard_method"/>
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
</service>
<service android:name="com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" /> <meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
</application> </application>

View File

@@ -19,7 +19,6 @@
*/ */
package com.kunzisoft.keepass.activities; package com.kunzisoft.keepass.activities;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
@@ -27,6 +26,7 @@ import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.TextView; import android.widget.TextView;
import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.stylish.StylishActivity; import com.kunzisoft.keepass.stylish.StylishActivity;
@@ -40,7 +40,7 @@ public class AboutActivity extends StylishActivity {
setContentView(R.layout.about); setContentView(R.layout.about);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.menu_about)); toolbar.setTitle(getString(R.string.menu_about));
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
assert getSupportActionBar() != null; assert getSupportActionBar() != null;
@@ -48,18 +48,25 @@ public class AboutActivity extends StylishActivity {
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
String version; String version;
String build;
try { try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
version = packageInfo.versionName; build = BuildConfig.BUILD_VERSION;
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
Log.w(getClass().getSimpleName(), "Unable to get application version", e); Log.w(getClass().getSimpleName(), "Unable to get the app or the build version", e);
version = "Unable to get application version."; version = "Unable to get the app version";
build = "Unable to get the build version";
} }
version = getString(R.string.version_label) + " " + version; version = getString(R.string.version_label, version);
TextView versionText = (TextView) findViewById(R.id.activity_about_version); TextView versionTextView = findViewById(R.id.activity_about_version);
versionText.setText(version); versionTextView.setText(version);
TextView disclaimerText = (TextView) findViewById(R.id.disclaimer); build = getString(R.string.build_label, build);
TextView buildTextView = findViewById(R.id.activity_about_build);
buildTextView.setText(build);
TextView disclaimerText = findViewById(R.id.disclaimer);
disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear())); disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear()));
} }

View File

@@ -28,6 +28,7 @@ import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
@@ -46,7 +47,6 @@ import com.kunzisoft.keepass.database.ExtraFields;
import com.kunzisoft.keepass.database.PwDatabase; import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry; import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.security.ProtectedString; import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.lock.LockingActivity; import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.lock.LockingHideActivity; import com.kunzisoft.keepass.lock.LockingHideActivity;
import com.kunzisoft.keepass.notifications.NotificationCopyingService; import com.kunzisoft.keepass.notifications.NotificationCopyingService;
@@ -65,6 +65,7 @@ import java.util.Date;
import java.util.UUID; import java.util.UUID;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isClipboardNotificationsEnable; import static com.kunzisoft.keepass.settings.PreferencesUtil.isClipboardNotificationsEnable;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields;
public class EntryActivity extends LockingHideActivity { public class EntryActivity extends LockingHideActivity {
private final static String TAG = EntryActivity.class.getName(); private final static String TAG = EntryActivity.class.getName();
@@ -78,15 +79,17 @@ public class EntryActivity extends LockingHideActivity {
protected PwEntry mEntry; protected PwEntry mEntry;
private boolean mShowPassword; private boolean mShowPassword;
protected boolean readOnly = false;
private ClipboardHelper clipboardHelper; private ClipboardHelper clipboardHelper;
private boolean firstLaunchOfActivity; private boolean firstLaunchOfActivity;
public static void launch(Activity act, PwEntry pw) { private int iconColor;
public static void launch(Activity act, PwEntry pw, boolean readOnly) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) { if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, EntryActivity.class); Intent intent = new Intent(act, EntryActivity.class);
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID())); intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE); act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
} }
} }
@@ -110,7 +113,7 @@ public class EntryActivity extends LockingHideActivity {
finish(); finish();
return; return;
} }
readOnly = db.isReadOnly(); readOnly = db.isReadOnly() || readOnly;
mShowPassword = !PreferencesUtil.isPasswordMask(this); mShowPassword = !PreferencesUtil.isPasswordMask(this);
@@ -123,6 +126,11 @@ public class EntryActivity extends LockingHideActivity {
finish(); finish();
return; return;
} }
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set // Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
invalidateOptionsMenu(); invalidateOptionsMenu();
@@ -225,7 +233,7 @@ public class EntryActivity extends LockingHideActivity {
startService(intent); startService(intent);
} }
mEntry.endToManageFieldReferences(); mEntry.stopToManageFieldReferences();
} }
firstLaunchOfActivity = false; firstLaunchOfActivity = false;
} }
@@ -310,18 +318,10 @@ public class EntryActivity extends LockingHideActivity {
mEntry.startToManageFieldReferences(pm); mEntry.startToManageFieldReferences(pm);
// Assign title icon // Assign title icon
if (IconPackChooser.getSelectedIconPack(this).tintable()) { db.getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), iconColor);
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon());
}
// Assign title text // Assign title text
titleView.setText(mEntry.getTitle()); titleView.setText(mEntry.getVisualTitle());
// Assign basic fields // Assign basic fields
entryContentsView.assignUserName(mEntry.getUsername()); entryContentsView.assignUserName(mEntry.getUsername());
@@ -330,12 +330,39 @@ public class EntryActivity extends LockingHideActivity {
getString(R.string.copy_field, getString(R.string.entry_user_name))) getString(R.string.copy_field, getString(R.string.entry_user_name)))
); );
entryContentsView.assignPassword(mEntry.getPassword()); boolean allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this);
if (PreferencesUtil.allowCopyPasswordAndProtectedFields(this)) { entryContentsView.assignPassword(mEntry.getPassword(), allowCopyPassword);
if (allowCopyPassword) {
entryContentsView.assignPasswordCopyListener(view -> entryContentsView.assignPasswordCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(), clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
getString(R.string.copy_field, getString(R.string.entry_password))) getString(R.string.copy_field, getString(R.string.entry_password)))
); );
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
entryContentsView.assignPasswordCopyListener(v -> {
String message = getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning);
AlertDialog warningDialog = new AlertDialog.Builder(EntryActivity.this)
.setMessage(message).create();
warningDialog.setButton(AlertDialog.BUTTON1, getText(android.R.string.ok),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, true);
dialog.dismiss();
fillData();
});
warningDialog.setButton(AlertDialog.BUTTON2, getText(android.R.string.cancel),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, false);
dialog.dismiss();
fillData();
});
warningDialog.show();
});
} else {
entryContentsView.assignPasswordCopyListener(null);
}
} }
entryContentsView.assignURL(mEntry.getUrl()); entryContentsView.assignURL(mEntry.getUrl());
@@ -369,7 +396,7 @@ public class EntryActivity extends LockingHideActivity {
entryContentsView.assignExpiresDate(getString(R.string.never)); entryContentsView.assignExpiresDate(getString(R.string.never));
} }
mEntry.endToManageFieldReferences(); mEntry.stopToManageFieldReferences();
} }
@Override @Override

View File

@@ -56,7 +56,6 @@ import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
import com.kunzisoft.keepass.database.security.ProtectedString; import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment; import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment; import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.lock.LockingActivity; import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.lock.LockingHideActivity; import com.kunzisoft.keepass.lock.LockingHideActivity;
import com.kunzisoft.keepass.settings.PreferencesUtil; import com.kunzisoft.keepass.settings.PreferencesUtil;
@@ -71,7 +70,7 @@ import java.util.UUID;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.UNDEFINED_ICON_ID; import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD;
public class EntryEditActivity extends LockingHideActivity public class EntryEditActivity extends LockingHideActivity
implements IconPickerDialogFragment.IconPickerListener, implements IconPickerDialogFragment.IconPickerListener,
@@ -89,10 +88,12 @@ public class EntryEditActivity extends LockingHideActivity
public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129; public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"; public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
private Database database;
protected PwEntry mEntry; protected PwEntry mEntry;
protected PwEntry mCallbackNewEntry; protected PwEntry mCallbackNewEntry;
protected boolean mIsNew; protected boolean mIsNew;
protected int mSelectedIconID = UNDEFINED_ICON_ID; protected PwIconStandard mSelectedIconStandard;
// Views // Views
private ScrollView scrollView; private ScrollView scrollView;
@@ -143,7 +144,6 @@ public class EntryEditActivity extends LockingHideActivity
setContentView(R.layout.entry_edit); setContentView(R.layout.entry_edit);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
assert getSupportActionBar() != null; assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -162,8 +162,8 @@ public class EntryEditActivity extends LockingHideActivity
entryExtraFieldsContainer = findViewById(R.id.advanced_container); entryExtraFieldsContainer = findViewById(R.id.advanced_container);
// Likely the app has been killed exit the activity // Likely the app has been killed exit the activity
Database db = App.getDB(); database = App.getDB();
if ( ! db.getLoaded() ) { if ( ! database.getLoaded() ) {
finish(); finish();
return; return;
} }
@@ -176,18 +176,16 @@ public class EntryEditActivity extends LockingHideActivity
TypedArray ta = getTheme().obtainStyledAttributes(attrs); TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE); iconColor = ta.getColor(0, Color.WHITE);
PwDatabase pm = db.getPwDatabase(); mSelectedIconStandard = database.getPwDatabase().getIconFactory().getUnknownIcon();
PwDatabase pm = database.getPwDatabase();
if ( uuidBytes == null ) { if ( uuidBytes == null ) {
PwGroupId parentId = (PwGroupId) intent.getSerializableExtra(KEY_PARENT); PwGroupId parentId = intent.getParcelableExtra(KEY_PARENT);
PwGroup parent = pm.getGroupByGroupId(parentId); PwGroup parent = pm.getGroupByGroupId(parentId);
mEntry = db.createEntry(parent); mEntry = database.createEntry(parent);
mIsNew = true; mIsNew = true;
// Add the default icon // Add the default icon
if (IconPackChooser.getSelectedIconPack(this).tintable()) { database.getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, iconColor);
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, true, iconColor);
} else {
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView);
}
} else { } else {
UUID uuid = Types.bytestoUUID(uuidBytes); UUID uuid = Types.bytestoUUID(uuidBytes);
mEntry = pm.getEntryByUUIDId(uuid); mEntry = pm.getEntryByUUIDId(uuid);
@@ -195,8 +193,12 @@ public class EntryEditActivity extends LockingHideActivity
fillData(); fillData();
} }
// Assign title
setTitle((mIsNew) ? getString(R.string.add_entry) : getString(R.string.edit_entry));
// Retrieve the icon after an orientation change // Retrieve the icon after an orientation change
if (savedInstanceState != null && savedInstanceState.containsKey(IconPickerDialogFragment.KEY_ICON_ID)) { if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ICON_STANDARD)) {
iconPicked(savedInstanceState); iconPicked(savedInstanceState);
} }
@@ -259,9 +261,9 @@ public class EntryEditActivity extends LockingHideActivity
EntryEditActivity act = EntryEditActivity.this; EntryEditActivity act = EntryEditActivity.this;
RunnableOnFinish task; RunnableOnFinish task;
if ( mIsNew ) { if ( mIsNew ) {
task = new AddEntryRunnable(act, App.getDB(), mCallbackNewEntry, onFinish); task = new AddEntryRunnable(act, database, mCallbackNewEntry, onFinish);
} else { } else {
task = new UpdateEntryRunnable(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish); task = new UpdateEntryRunnable(act, database, mEntry, mCallbackNewEntry, onFinish);
} }
task.setUpdateProgressTaskStatus( task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this, new UpdateProgressTaskStatus(this,
@@ -409,7 +411,7 @@ public class EntryEditActivity extends LockingHideActivity
newEntry.setLastModificationTime(new PwDate()); newEntry.setLastModificationTime(new PwDate());
newEntry.setTitle(entryTitleView.getText().toString()); newEntry.setTitle(entryTitleView.getText().toString());
newEntry.setIcon(retrieveIcon()); newEntry.setIconStandard(retrieveIcon());
newEntry.setUrl(entryUrlView.getText().toString()); newEntry.setUrl(entryUrlView.getText().toString());
newEntry.setUsername(entryUserNameView.getText().toString()); newEntry.setUsername(entryUserNameView.getText().toString());
@@ -429,21 +431,21 @@ public class EntryEditActivity extends LockingHideActivity
} }
} }
newEntry.endToManageFieldReferences(); newEntry.stopToManageFieldReferences();
return newEntry; return newEntry;
} }
/** /**
* Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one * Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
* @return
*/ */
private PwIconStandard retrieveIcon() { private PwIconStandard retrieveIcon() {
if(mSelectedIconID != UNDEFINED_ICON_ID)
return App.getDB().getPwDatabase().getIconFactory().getIcon(mSelectedIconID); if (!mSelectedIconStandard.isUnknown())
return mSelectedIconStandard;
else { else {
if (mIsNew) { if (mIsNew) {
return App.getDB().getPwDatabase().getIconFactory().getKeyIcon(); return database.getPwDatabase().getIconFactory().getKeyIcon();
} }
else { else {
// Keep previous icon, if no new one was selected // Keep previous icon, if no new one was selected
@@ -475,16 +477,21 @@ public class EntryEditActivity extends LockingHideActivity
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private void assignIconView() {
database.getDrawFactory()
.assignDatabaseIconTo(
this,
entryIconView,
mEntry.getIcon(),
iconColor);
}
protected void fillData() { protected void fillData() {
if (IconPackChooser.getSelectedIconPack(this).tintable()) { assignIconView();
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon());
}
// Don't start the field reference manager, we want to see the raw ref // Don't start the field reference manager, we want to see the raw ref
mEntry.endToManageFieldReferences(); mEntry.stopToManageFieldReferences();
entryTitleView.setText(mEntry.getTitle()); entryTitleView.setText(mEntry.getTitle());
entryUserNameView.setText(mEntry.getUsername()); entryUserNameView.setText(mEntry.getUsername());
@@ -515,14 +522,15 @@ public class EntryEditActivity extends LockingHideActivity
@Override @Override
public void iconPicked(Bundle bundle) { public void iconPicked(Bundle bundle) {
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID); mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD);
entryIconView.setImageResource(IconPackChooser.getSelectedIconPack(this).iconToResId(mSelectedIconID)); mEntry.setIconStandard(mSelectedIconStandard);
assignIconView();
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
if (mSelectedIconID != UNDEFINED_ICON_ID) { if (!mSelectedIconStandard.isUnknown()) {
outState.putInt(IconPickerDialogFragment.KEY_ICON_ID, mSelectedIconID); outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard);
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }
} }
@@ -548,7 +556,7 @@ public class EntryEditActivity extends LockingHideActivity
if (mCallbackNewEntry != null) { if (mCallbackNewEntry != null) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
Intent intentEntry = new Intent(); Intent intentEntry = new Intent();
bundle.putSerializable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry); bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
intentEntry.putExtras(bundle); intentEntry.putExtras(bundle);
if (mIsNew) { if (mIsNew) {
setResult(ADD_ENTRY_RESULT_CODE, intentEntry); setResult(ADD_ENTRY_RESULT_CODE, intentEntry);

View File

@@ -19,6 +19,7 @@
*/ */
package com.kunzisoft.keepass.activities; package com.kunzisoft.keepass.activities;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.app.SearchManager; import android.app.SearchManager;
@@ -29,24 +30,31 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import com.getkeepsafe.taptargetview.TapTarget; import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView; import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter; import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper; import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.Database; import com.kunzisoft.keepass.database.Database;
@@ -54,9 +62,11 @@ import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry; import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup; import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId; import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.database.PwIcon; import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwIconStandard; import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.PwNode; import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.database.action.node.AddGroupRunnable; import com.kunzisoft.keepass.database.action.node.AddGroupRunnable;
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish; import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable; import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable;
@@ -69,108 +79,178 @@ import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment; import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment; import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
import com.kunzisoft.keepass.dialogs.ReadOnlyDialog; import com.kunzisoft.keepass.dialogs.ReadOnlyDialog;
import com.kunzisoft.keepass.icons.IconPackChooser; import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.password.AssignPasswordHelper;
import com.kunzisoft.keepass.selection.EntrySelectionHelper; import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.search.SearchResultsActivity;
import com.kunzisoft.keepass.settings.PreferencesUtil; import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment; import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
import com.kunzisoft.keepass.tasks.UIToastTask; import com.kunzisoft.keepass.tasks.UIToastTask;
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus; import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
import com.kunzisoft.keepass.utils.MenuUtil;
import com.kunzisoft.keepass.view.AddNodeButtonView; import com.kunzisoft.keepass.view.AddNodeButtonView;
import net.cachapa.expandablelayout.ExpandableLayout; import net.cachapa.expandablelayout.ExpandableLayout;
public class GroupActivity extends ListNodesActivity import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
public class GroupActivity extends LockingActivity
implements GroupEditDialogFragment.EditGroupListener, implements GroupEditDialogFragment.EditGroupListener,
IconPickerDialogFragment.IconPickerListener, IconPickerDialogFragment.IconPickerListener,
NodeAdapter.NodeMenuListener, NodeAdapter.NodeMenuListener,
ListNodesFragment.OnScrollListener { ListNodesFragment.OnScrollListener,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
private static final String TAG = GroupActivity.class.getName(); private static final String TAG = GroupActivity.class.getName();
private Toolbar toolbar; private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
private static final String SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG";
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY";
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
private Toolbar toolbar;
private View searchTitleView;
private ExpandableLayout toolbarPasteExpandableLayout; private ExpandableLayout toolbarPasteExpandableLayout;
private Toolbar toolbarPaste; private Toolbar toolbarPaste;
private ImageView iconView; private ImageView iconView;
private AddNodeButtonView addNodeButtonView; private AddNodeButtonView addNodeButtonView;
private TextView groupNameView;
protected boolean addGroupEnabled = false; private Database database;
protected boolean addEntryEnabled = false;
protected boolean isRoot = false;
protected boolean readOnly = false;
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"; private ListNodesFragment listNodesFragment;
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY"; private boolean currentGroupIsASearch;
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
private PwGroup rootGroup;
private PwGroup mCurrentGroup;
private PwGroup oldGroupToUpdate; private PwGroup oldGroupToUpdate;
private PwNode nodeToCopy; private PwNode nodeToCopy;
private PwNode nodeToMove; private PwNode nodeToMove;
public static void launch(Activity act) { private boolean entrySelectionMode;
private AutofillHelper autofillHelper;
private SearchEntryCursorAdapter searchSuggestionAdapter;
private int iconColor;
// After a database creation
public static void launch(Activity act) {
launch(act, READ_ONLY_DEFAULT);
}
public static void launch(Activity act, boolean readOnly) {
startRecordTime(act); startRecordTime(act);
launch(act, null); launch(act, null, readOnly);
} }
public static void launch(Activity act, PwGroup group) { private static void buildAndLaunchIntent(Activity activity, PwGroup group, boolean readOnly,
if (checkTimeIsAllowedOrFinish(act)) { IntentBuildLauncher intentBuildLauncher) {
Intent intent = new Intent(act, GroupActivity.class); if (checkTimeIsAllowedOrFinish(activity)) {
Intent intent = new Intent(activity, GroupActivity.class);
if (group != null) { if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId()); intent.putExtra(GROUP_ID_KEY, group.getId());
} }
act.startActivityForResult(intent, 0); ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
intentBuildLauncher.startActivityForResult(intent);
} }
} }
public static void launchForKeyboardResult(Activity act) { public static void launch(Activity activity, PwGroup group, boolean readOnly) {
buildAndLaunchIntent(activity, group, readOnly,
(intent) -> activity.startActivityForResult(intent, 0));
}
public static void launchForKeyboardResult(Activity act, boolean readOnly) {
startRecordTime(act); startRecordTime(act);
launchForKeyboardResult(act, null); launchForKeyboardResult(act, null, readOnly);
} }
public static void launchForKeyboardResult(Activity act, PwGroup group) { public static void launchForKeyboardResult(Activity activity, PwGroup group, boolean readOnly) {
// TODO remove // TODO implement pre search to directly open the direct group
if (checkTimeIsAllowedOrFinish(act)) { buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
Intent intent = new Intent(act, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent); EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE); activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
} });
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
public static void launchForAutofillResult(Activity act, AssistStructure assistStructure) { public static void launchForAutofillResult(Activity act, AssistStructure assistStructure, boolean readOnly) {
if ( assistStructure != null ) { if ( assistStructure != null ) {
startRecordTime(act); startRecordTime(act);
launchForAutofillResult(act, null, assistStructure); launchForAutofillResult(act, null, assistStructure, readOnly);
} else { } else {
launch(act); launch(act, readOnly);
} }
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
public static void launchForAutofillResult(Activity act, PwGroup group, AssistStructure assistStructure) { public static void launchForAutofillResult(Activity activity, PwGroup group, AssistStructure assistStructure, boolean readOnly) {
// TODO remove // TODO implement pre search to directly open the direct group
if ( assistStructure != null ) { if ( assistStructure != null ) {
if (checkTimeIsAllowedOrFinish(act)) { buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
Intent intent = new Intent(act, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure); AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE); activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
} });
} else { } else {
launch(act, group); launch(activity, group, readOnly);
} }
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if ( isFinishing() ) {
return;
}
database = App.getDB();
// Likely the app has been killed exit the activity
if ( ! database.getLoaded() ) {
finish();
return;
}
// Construct main view
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
// Initialize views
iconView = findViewById(R.id.icon);
addNodeButtonView = findViewById(R.id.add_node_button);
toolbar = findViewById(R.id.toolbar);
searchTitleView = findViewById(R.id.search_title);
groupNameView = findViewById(R.id.group_name);
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
toolbarPaste = findViewById(R.id.toolbar_paste);
invalidateOptionsMenu();
// Get arg from intent or instance state
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
// Retrieve elements after an orientation change
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
oldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY);
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) {
nodeToCopy = savedInstanceState.getParcelable(NODE_TO_COPY_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener());
}
else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
nodeToMove = savedInstanceState.getParcelable(NODE_TO_MOVE_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
}
}
rootGroup = database.getPwDatabase().getRootGroup();
mCurrentGroup = retrieveCurrentGroup(getIntent(), savedInstanceState);
currentGroupIsASearch = Intent.ACTION_SEARCH.equals(getIntent().getAction());
Log.i(TAG, "Started creating tree"); Log.i(TAG, "Started creating tree");
if ( mCurrentGroup == null ) { if ( mCurrentGroup == null ) {
@@ -178,23 +258,9 @@ public class GroupActivity extends ListNodesActivity
return; return;
} }
// Construct main view
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
attachFragmentToContentView();
iconView = findViewById(R.id.icon);
addNodeButtonView = findViewById(R.id.add_node_button);
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(""); toolbar.setTitle("");
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
groupNameView = findViewById(R.id.group_name);
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
toolbarPaste = findViewById(R.id.toolbar_paste);
toolbarPaste.inflateMenu(R.menu.node_paste_menu); toolbarPaste.inflateMenu(R.menu.node_paste_menu);
toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp); toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
toolbarPaste.setNavigationOnClickListener(view -> { toolbarPaste.setNavigationOnClickListener(view -> {
@@ -203,104 +269,216 @@ public class GroupActivity extends ListNodesActivity
nodeToMove = null; nodeToMove = null;
}); });
if (savedInstanceState != null) { // Retrieve the textColor to tint the icon
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY)) int[] attrs = {R.attr.textColorInverse};
oldGroupToUpdate = (PwGroup) savedInstanceState.getSerializable(OLD_GROUP_TO_UPDATE_KEY); TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) { String fragmentTag = LIST_NODES_FRAGMENT_TAG;
nodeToCopy = (PwNode) savedInstanceState.getSerializable(NODE_TO_COPY_KEY); if (currentGroupIsASearch)
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener()); fragmentTag = SEARCH_FRAGMENT_TAG;
}
else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
nodeToMove = (PwNode) savedInstanceState.getSerializable(NODE_TO_MOVE_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
}
}
addNodeButtonView.setAddGroupClickListener(v -> { // Initialize the fragment with the list
GroupEditDialogFragment.build() listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.show(getSupportFragmentManager(), .findFragmentByTag(fragmentTag);
GroupEditDialogFragment.TAG_CREATE_GROUP); if (listNodesFragment == null)
}); listNodesFragment = ListNodesFragment.newInstance(mCurrentGroup, readOnly, currentGroupIsASearch);
// Attach fragment to content view
getSupportFragmentManager().beginTransaction().replace(
R.id.nodes_list_fragment_container,
listNodesFragment,
fragmentTag)
.commit();
// Add listeners to the add buttons
addNodeButtonView.setAddGroupClickListener(v -> GroupEditDialogFragment.build()
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP));
addNodeButtonView.setAddEntryClickListener(v -> addNodeButtonView.setAddEntryClickListener(v ->
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup)); EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
Log.i(TAG, "Finished creating tree"); // To init autofill
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
if (isRoot) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
showWarnings(); autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
} }
// Search suggestion
searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database);
Log.i(TAG, "Finished creating tree");
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onNewIntent(Intent intent) {
outState.putSerializable(GROUP_ID_KEY, mCurrentGroup.getId()); setIntent(intent);
outState.putSerializable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate); if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
if (nodeToCopy != null) // only one instance of search in backstack
outState.putSerializable(NODE_TO_COPY_KEY, nodeToCopy); openSearchGroup(retrieveCurrentGroup(intent, null));
if (nodeToMove != null) currentGroupIsASearch = true;
outState.putSerializable(NODE_TO_MOVE_KEY, nodeToMove); } else {
super.onSaveInstanceState(outState); currentGroupIsASearch = false;
}
} }
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) { private void openSearchGroup(PwGroup group) {
// Delete the previous search fragment
PwGroupId pwGroupId = null; // TODO Parcelable Fragment searchFragment = getSupportFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG);
if (savedInstanceState != null if (searchFragment != null) {
&& savedInstanceState.containsKey(GROUP_ID_KEY)) { if ( getSupportFragmentManager()
pwGroupId = (PwGroupId) savedInstanceState.getSerializable(GROUP_ID_KEY); .popBackStackImmediate(SEARCH_FRAGMENT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) )
} else { getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
if (getIntent() != null)
pwGroupId = (PwGroupId) getIntent().getSerializableExtra(GROUP_ID_KEY);
} }
Database db = App.getDB(); openGroup(group, true);
readOnly = db.isReadOnly(); }
PwGroup root = db.getPwDatabase().getRootGroup();
Log.w(TAG, "Creating tree view"); private void openChildGroup(PwGroup group) {
PwGroup currentGroup; openGroup(group, false);
if ( pwGroupId == null ) { }
currentGroup = root;
} else { private void openGroup(PwGroup group, boolean isASearch) {
currentGroup = db.getPwDatabase().getGroupByGroupId(pwGroupId); // Check Timeout
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
// Open a group in a new fragment
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group, readOnly, isASearch);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// Different animation
String fragmentTag;
if (isASearch) {
fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_out_bottom,
R.anim.slide_in_bottom, R.anim.slide_out_top);
fragmentTag = SEARCH_FRAGMENT_TAG;
} else {
fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right);
fragmentTag = LIST_NODES_FRAGMENT_TAG;
}
fragmentTransaction.replace(R.id.nodes_list_fragment_container,
newListNodeFragment,
fragmentTag);
fragmentTransaction.addToBackStack(fragmentTag);
fragmentTransaction.commit();
listNodesFragment = newListNodeFragment;
mCurrentGroup = group;
assignGroupViewElements();
} }
if (currentGroup != null) {
addGroupEnabled = !readOnly;
addEntryEnabled = !readOnly; // TODO consultation mode
isRoot = (currentGroup == root);
if (!currentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled;
}
return currentGroup;
} }
@Override @Override
public void assignToolbarElements() { protected void onSaveInstanceState(Bundle outState) {
super.assignToolbarElements(); if (mCurrentGroup != null)
outState.putParcelable(GROUP_ID_KEY, mCurrentGroup.getId());
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
if (nodeToCopy != null)
outState.putParcelable(NODE_TO_COPY_KEY, nodeToCopy);
if (nodeToMove != null)
outState.putParcelable(NODE_TO_MOVE_KEY, nodeToMove);
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
// Assign the group icon depending of IconPack or custom icon protected PwGroup retrieveCurrentGroup(Intent intent, @Nullable Bundle savedInstanceState) {
if ( mCurrentGroup != null ) {
if (IconPackChooser.getSelectedIconPack(this).tintable()) { // If it's a search
// Retrieve the textColor to tint the icon if ( Intent.ACTION_SEARCH.equals(intent.getAction()) ) {
int[] attrs = {R.attr.textColorInverse}; return database.search(intent.getStringExtra(SearchManager.QUERY).trim());
TypedArray ta = getTheme().obtainStyledAttributes(attrs); }
int iconColor = ta.getColor(0, Color.WHITE); // else a real group
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor); else {
PwGroupId pwGroupId = null;
if (savedInstanceState != null
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY);
} else { } else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon()); if (getIntent() != null)
pwGroupId = intent.getParcelableExtra(GROUP_ID_KEY);
} }
if (toolbar != null) { readOnly = database.isReadOnly() || readOnly; // Force read only if the database is like that
if ( mCurrentGroup.containsParent() )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp); Log.w(TAG, "Creating tree view");
else { PwGroup currentGroup;
toolbar.setNavigationIcon(null); if (pwGroupId == null) {
currentGroup = rootGroup;
} else {
currentGroup = database.getPwDatabase().getGroupByGroupId(pwGroupId);
}
return currentGroup;
}
}
public void assignGroupViewElements() {
// Assign title
if (mCurrentGroup != null) {
String title = mCurrentGroup.getName();
if (title != null && title.length() > 0) {
if (groupNameView != null) {
groupNameView.setText(title);
groupNameView.invalidate();
}
} else {
if (groupNameView != null) {
groupNameView.setText(getText(R.string.root));
groupNameView.invalidate();
} }
} }
} }
if (currentGroupIsASearch) {
searchTitleView.setVisibility(View.VISIBLE);
} else {
searchTitleView.setVisibility(View.GONE);
}
// Assign icon
if (currentGroupIsASearch) {
if (toolbar != null) {
toolbar.setNavigationIcon(null);
}
iconView.setVisibility(View.GONE);
} else {
// Assign the group icon depending of IconPack or custom icon
iconView.setVisibility(View.VISIBLE);
if (mCurrentGroup != null) {
database.getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), iconColor);
if (toolbar != null) {
if (mCurrentGroup.containsParent())
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
else {
toolbar.setNavigationIcon(null);
}
}
}
}
// Show button if allowed
if (addNodeButtonView != null) {
// To enable add button
boolean addGroupEnabled = !readOnly && !currentGroupIsASearch;
boolean addEntryEnabled = !readOnly && !currentGroupIsASearch;
if (mCurrentGroup != null) {
boolean isRoot = (mCurrentGroup == rootGroup);
if (!mCurrentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled;
if (isRoot) {
showWarnings();
}
}
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
if (addNodeButtonView.isEnable())
addNodeButtonView.showButton();
}
} }
@Override @Override
@@ -309,6 +487,50 @@ public class GroupActivity extends ListNodesActivity
addNodeButtonView.hideButtonOnScrollListener(dy); addNodeButtonView.hideButtonOnScrollListener(dy);
} }
@Override
public void onNodeClick(PwNode node) {
// Add event when we have Autofill
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
// Build response with the entry selected
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
}
}
if ( assistStructure == null ){
if (entrySelectionMode) {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
} else {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node, readOnly);
break;
}
}
}
}
@Override @Override
public boolean onOpenMenuClick(PwNode node) { public boolean onOpenMenuClick(PwNode node) {
onNodeClick(node); onNodeClick(node);
@@ -320,7 +542,7 @@ public class GroupActivity extends ListNodesActivity
switch (node.getType()) { switch (node.getType()) {
case GROUP: case GROUP:
oldGroupToUpdate = (PwGroup) node; oldGroupToUpdate = (PwGroup) node;
GroupEditDialogFragment.build(node) GroupEditDialogFragment.build(oldGroupToUpdate)
.show(getSupportFragmentManager(), .show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP); GroupEditDialogFragment.TAG_CREATE_GROUP);
break; break;
@@ -481,9 +703,11 @@ public class GroupActivity extends ListNodesActivity
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
// Show button on resume // Refresh the elements
if (addNodeButtonView != null) assignGroupViewElements();
addNodeButtonView.showButton(); // Refresh suggestions to change preferences
if (searchSuggestionAdapter != null)
searchSuggestionAdapter.reInit(this);
} }
/** /**
@@ -496,7 +720,8 @@ public class GroupActivity extends ListNodesActivity
// If no node, show education to add new one // If no node, show education to add new one
if (listNodesFragment != null if (listNodesFragment != null
&& listNodesFragment.isEmpty()) { && listNodesFragment.isEmpty()) {
if (!PreferencesUtil.isEducationNewNodePerformed(this)) { if (!PreferencesUtil.isEducationNewNodePerformed(this)
&& addNodeButtonView.isEnable()) {
TapTargetView.showFor(this, TapTargetView.showFor(this,
TapTarget.forView(findViewById(R.id.add_button), TapTarget.forView(findViewById(R.id.add_button),
@@ -633,7 +858,8 @@ public class GroupActivity extends ListNodesActivity
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search, menu); inflater.inflate(R.menu.search, menu);
inflater.inflate(R.menu.database_master_key, menu); if (!readOnly)
inflater.inflate(R.menu.database_master_key, menu);
inflater.inflate(R.menu.database_lock, menu); inflater.inflate(R.menu.database_lock, menu);
// Get the SearchView and set the searchable configuration // Get the SearchView and set the searchable configuration
@@ -646,11 +872,26 @@ public class GroupActivity extends ListNodesActivity
searchView = (SearchView) searchItem.getActionView(); searchView = (SearchView) searchItem.getActionView();
} }
if (searchView != null) { if (searchView != null) {
// TODO Flickering when locking, will be better with content provider searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, GroupActivity.class)));
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
searchView.setSuggestionsAdapter(searchSuggestionAdapter);
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
@Override
public boolean onSuggestionClick(int position) {
onNodeClick(searchSuggestionAdapter.getEntryFromPosition(position));
return true;
}
@Override
public boolean onSuggestionSelect(int position) {
return true;
}
});
} }
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
// Launch education screen // Launch education screen
@@ -667,16 +908,23 @@ public class GroupActivity extends ListNodesActivity
if (Intent.ACTION_SEARCH.equals(intent.getAction())) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY); String query = intent.getStringExtra(SearchManager.QUERY);
// manually launch the real search activity // manually launch the real search activity
final Intent searchIntent = new Intent(getApplicationContext(), SearchResultsActivity.class); final Intent searchIntent = new Intent(getApplicationContext(), GroupActivity.class);
// add query to the Intent Extras // add query to the Intent Extras
searchIntent.setAction(Intent.ACTION_SEARCH); searchIntent.setAction(Intent.ACTION_SEARCH);
searchIntent.putExtra(SearchManager.QUERY, query); searchIntent.putExtra(SearchManager.QUERY, query);
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& autofillHelper.getAssistStructure() != null ) { && autofillHelper.getAssistStructure() != null ) {
AutofillHelper.addAssistStructureExtraInIntent(searchIntent, autofillHelper.getAssistStructure()); AutofillHelper.addAssistStructureExtraInIntent(searchIntent, autofillHelper.getAssistStructure());
startActivityForResult(searchIntent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE); startActivityForResult(searchIntent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
customSearchQueryExecuted = true; customSearchQueryExecuted = true;
} }
// To get the keyboard response, verify if the current intent contains the EntrySelection key
else if (EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent())){
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(searchIntent);
startActivityForResult(searchIntent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
customSearchQueryExecuted = true;
}
} }
if (!customSearchQueryExecuted) { if (!customSearchQueryExecuted) {
@@ -693,7 +941,7 @@ public class GroupActivity extends ListNodesActivity
return true; return true;
case R.id.menu_search: case R.id.menu_search:
onSearchRequested(); //onSearchRequested();
return true; return true;
case R.id.menu_lock: case R.id.menu_lock:
@@ -703,8 +951,11 @@ public class GroupActivity extends ListNodesActivity
case R.id.menu_change_master_key: case R.id.menu_change_master_key:
setPassword(); setPassword();
return true; return true;
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
return super.onOptionsItemSelected(item);
} }
return super.onOptionsItemSelected(item);
} }
private void setPassword() { private void setPassword() {
@@ -728,7 +979,7 @@ public class GroupActivity extends ListNodesActivity
try { try {
iconStandard = (PwIconStandard) icon; iconStandard = (PwIconStandard) icon;
} catch (Exception ignored) {} // TODO custom icon } catch (Exception ignored) {} // TODO custom icon
newGroup.setIcon(iconStandard); newGroup.setIconStandard(iconStandard);
// If group created save it in the database // If group created save it in the database
AddGroupRunnable addGroupRunnable = new AddGroupRunnable(this, AddGroupRunnable addGroupRunnable = new AddGroupRunnable(this,
@@ -750,8 +1001,11 @@ public class GroupActivity extends ListNodesActivity
updateGroup.setName(name); updateGroup.setName(name);
try { try {
iconStandard = (PwIconStandard) icon; iconStandard = (PwIconStandard) icon;
} catch (Exception ignored) {} // TODO custom icon updateGroup = ((PwGroupV4) oldGroupToUpdate).clone(); // TODO generalize
updateGroup.setIcon(iconStandard); } catch (Exception e) {
e.printStackTrace();
} // TODO custom icon
updateGroup.setIconStandard(iconStandard);
if (listNodesFragment != null) if (listNodesFragment != null)
listNodesFragment.removeNode(oldGroupToUpdate); listNodesFragment.removeNode(oldGroupToUpdate);
@@ -776,6 +1030,7 @@ public class GroupActivity extends ListNodesActivity
class AfterAddNode extends AfterActionNodeOnFinish { class AfterAddNode extends AfterActionNodeOnFinish {
@Override
public void run(PwNode oldNode, PwNode newNode) { public void run(PwNode oldNode, PwNode newNode) {
super.run(); super.run();
@@ -794,6 +1049,7 @@ public class GroupActivity extends ListNodesActivity
class AfterUpdateNode extends AfterActionNodeOnFinish { class AfterUpdateNode extends AfterActionNodeOnFinish {
@Override
public void run(PwNode oldNode, PwNode newNode) { public void run(PwNode oldNode, PwNode newNode) {
super.run(); super.run();
@@ -879,16 +1135,79 @@ public class GroupActivity extends ListNodesActivity
} }
@Override @Override
protected void openGroup(PwGroup group) { public void onAssignKeyDialogPositiveClick(
super.openGroup(group); boolean masterPasswordChecked, String masterPassword,
if (addNodeButtonView != null) boolean keyFileChecked, Uri keyFile) {
addNodeButtonView.showButton();
AssignPasswordHelper assignPasswordHelper =
new AssignPasswordHelper(this,
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
assignPasswordHelper.assignPasswordInDatabase(null);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
if (listNodesFragment != null)
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}
@SuppressLint("RestrictedApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.startActivityForResult(intent, requestCode, options);
}
}
private void removeSearchInIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
currentGroupIsASearch = false;
intent.setAction(Intent.ACTION_DEFAULT);
intent.removeExtra(SearchManager.QUERY);
}
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
super.onBackPressed(); if (checkTimeIsAllowedOrFinish(this)) {
if (addNodeButtonView != null) startRecordTime(this);
addNodeButtonView.showButton();
super.onBackPressed();
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// to refresh fragment
listNodesFragment.rebuildList();
mCurrentGroup = listNodesFragment.getMainGroup();
removeSearchInIntent(getIntent());
assignGroupViewElements();
}
} }
} }

View File

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

View File

@@ -1,290 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities;
import android.annotation.SuppressLint;
import android.app.assist.AssistStructure;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.password.AssignPasswordHelper;
import com.kunzisoft.keepass.utils.MenuUtil;
public abstract class ListNodesActivity extends LockingActivity
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
protected ListNodesFragment listNodesFragment;
protected PwGroup mCurrentGroup;
protected TextView groupNameView;
protected boolean entrySelectionMode;
protected AutofillHelper autofillHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( isFinishing() ) {
return;
}
// Likely the app has been killed exit the activity
if ( ! App.getDB().getLoaded() ) {
finish();
return;
}
invalidateOptionsMenu();
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
initializeListNodesFragment(mCurrentGroup);
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
}
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
@Override
protected void onResume() {
super.onResume();
// Refresh the title
assignToolbarElements();
}
protected void initializeListNodesFragment(PwGroup currentGroup) {
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
if (listNodesFragment == null)
listNodesFragment = ListNodesFragment.newInstance(currentGroup.getId());
}
/**
* Attach the fragment's list of node.
* <br />
* <strong>R.id.nodes_list_fragment_container</strong> must be the id of the container
*/
protected void attachFragmentToContentView() {
getSupportFragmentManager().beginTransaction().replace(
R.id.nodes_list_fragment_container,
listNodesFragment,
LIST_NODES_FRAGMENT_TAG)
.commit();
}
public void assignToolbarElements() {
if (mCurrentGroup != null) {
String title = mCurrentGroup.getName();
if (title != null && title.length() > 0) {
if (groupNameView != null) {
groupNameView.setText(title);
groupNameView.invalidate();
}
} else {
if (groupNameView != null) {
groupNameView.setText(getText(R.string.root));
groupNameView.invalidate();
}
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
return super.onOptionsItemSelected(item);
}
}
@Override
public void onNodeClick(PwNode node) {
// Add event when we have Autofill
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
// Build response with the entry selected
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
}
}
if ( assistStructure == null ){
if (entrySelectionMode) {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
} else {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node);
break;
}
}
}
}
protected void openGroup(PwGroup group) {
// Check Timeout
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group.getId());
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.nodes_list_fragment_container,
newListNodeFragment,
LIST_NODES_FRAGMENT_TAG)
.addToBackStack(LIST_NODES_FRAGMENT_TAG)
.commit();
listNodesFragment = newListNodeFragment;
mCurrentGroup = group;
assignToolbarElements();
}
}
@Override
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
AssignPasswordHelper assignPasswordHelper =
new AssignPasswordHelper(this,
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
assignPasswordHelper.assignPasswordInDatabase(null);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
if (listNodesFragment != null)
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}
@SuppressLint("RestrictedApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.startActivityForResult(intent, requestCode, options);
}
}
@Override
public void onBackPressed() {
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
super.onBackPressed();
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// to refresh fragment
listNodesFragment.rebuildList();
mCurrentGroup = listNodesFragment.getMainGroup();
assignToolbarElements();
}
}
}

View File

@@ -20,10 +20,8 @@ import android.view.ViewGroup;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter; import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabase; import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwGroup; import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwNode; import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum; import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.dialogs.SortDialogFragment; import com.kunzisoft.keepass.dialogs.SortDialogFragment;
@@ -35,24 +33,32 @@ public class ListNodesFragment extends StylishFragment implements
private static final String TAG = ListNodesFragment.class.getName(); private static final String TAG = ListNodesFragment.class.getName();
private static final String GROUP_ID_KEY = "GROUP_ID_KEY"; private static final String GROUP_KEY = "GROUP_KEY";
private static final String IS_SEARCH = "IS_SEARCH";
private NodeAdapter.NodeClickCallback nodeClickCallback; private NodeAdapter.NodeClickCallback nodeClickCallback;
private NodeAdapter.NodeMenuListener nodeMenuListener; private NodeAdapter.NodeMenuListener nodeMenuListener;
private OnScrollListener onScrollListener; private OnScrollListener onScrollListener;
private RecyclerView listView; private RecyclerView listView;
protected PwGroup mCurrentGroup; private PwGroup currentGroup;
protected NodeAdapter mAdapter; private NodeAdapter mAdapter;
private View notFoundView;
private boolean isASearchResult;
// Preferences for sorting // Preferences for sorting
private SharedPreferences prefs; private SharedPreferences prefs;
public static ListNodesFragment newInstance(PwGroupId groupId) { private boolean readOnly;
Bundle bundle=new Bundle();
if (groupId != null) { public static ListNodesFragment newInstance(PwGroup group, boolean readOnly, boolean isASearch) {
bundle.putSerializable(GROUP_ID_KEY, groupId); Bundle bundle = new Bundle();
if (group != null) {
bundle.putParcelable(GROUP_KEY, group);
} }
bundle.putBoolean(IS_SEARCH, isASearch);
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly);
ListNodesFragment listNodesFragment = new ListNodesFragment(); ListNodesFragment listNodesFragment = new ListNodesFragment();
listNodesFragment.setArguments(bundle); listNodesFragment.setArguments(bundle);
return listNodesFragment; return listNodesFragment;
@@ -90,42 +96,39 @@ public class ListNodesFragment extends StylishFragment implements
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); if ( getActivity() != null ) {
setHasOptionsMenu(true);
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
if (getArguments() != null) {
// Contains all the group in element
if (getArguments().containsKey(GROUP_KEY)) {
currentGroup = getArguments().getParcelable(GROUP_KEY);
}
if (getArguments().containsKey(IS_SEARCH)) {
isASearchResult = getArguments().getBoolean(IS_SEARCH);
}
}
mCurrentGroup = initCurrentGroup();
if (getActivity() != null) {
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater()); mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
mAdapter.setReadOnly(readOnly);
mAdapter.setIsASearchResult(isASearchResult);
mAdapter.setOnNodeClickListener(nodeClickCallback); mAdapter.setOnNodeClickListener(nodeClickCallback);
if (nodeMenuListener != null) { if (nodeMenuListener != null) {
mAdapter.setActivateContextMenu(true); mAdapter.setActivateContextMenu(true);
mAdapter.setNodeMenuListener(nodeMenuListener); mAdapter.setNodeMenuListener(nodeMenuListener);
} }
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
} }
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
} }
protected PwGroup initCurrentGroup() { // TODO Change by parcelable @Override
public void onSaveInstanceState(@NonNull Bundle outState) {
Database db = App.getDB(); ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
PwGroup root = db.getPwDatabase().getRootGroup(); super.onSaveInstanceState(outState);
PwGroup currentGroup = null;
if (getArguments() != null) {
// Contains only the group id, so the group must be retrieve
if (getArguments().containsKey(GROUP_ID_KEY)) {
PwGroupId pwGroupId = (PwGroupId) getArguments().getSerializable(GROUP_ID_KEY);
if ( pwGroupId != null )
currentGroup = db.getPwDatabase().getGroupByGroupId(pwGroupId);
}
}
if ( currentGroup == null ) {
currentGroup = root;
}
return currentGroup;
} }
@Nullable @Nullable
@@ -137,6 +140,7 @@ public class ListNodesFragment extends StylishFragment implements
View rootView = inflater.cloneInContext(getContextThemed()) View rootView = inflater.cloneInContext(getContextThemed())
.inflate(R.layout.list_nodes_fragment, container, false); .inflate(R.layout.list_nodes_fragment, container, false);
listView = rootView.findViewById(R.id.nodes_list); listView = rootView.findViewById(R.id.nodes_list);
notFoundView = rootView.findViewById(R.id.not_found_container);
if (onScrollListener != null) { if (onScrollListener != null) {
listView.addOnScrollListener(new RecyclerView.OnScrollListener() { listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -156,11 +160,21 @@ public class ListNodesFragment extends StylishFragment implements
super.onResume(); super.onResume();
rebuildList(); rebuildList();
if (isASearchResult && mAdapter.isEmpty()) {
// To show the " no search entry found "
listView.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listView.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
} }
public void rebuildList() { public void rebuildList() {
// Add elements to the list // Add elements to the list
mAdapter.rebuildList(mCurrentGroup); if (currentGroup != null)
mAdapter.rebuildList(currentGroup);
assignListToNodeAdapter(listView); assignListToNodeAdapter(listView);
} }
@@ -182,7 +196,7 @@ public class ListNodesFragment extends StylishFragment implements
// Tell the adapter to refresh it's list // Tell the adapter to refresh it's list
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore); mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
mAdapter.rebuildList(mCurrentGroup); mAdapter.rebuildList(currentGroup);
} }
@Override @Override
@@ -234,13 +248,13 @@ public class ListNodesFragment extends StylishFragment implements
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE: case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE || if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) { resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
PwNode newNode = (PwNode) data.getSerializableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY); PwNode newNode = data.getParcelableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
if (newNode != null) { if (newNode != null) {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE) if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter.addNode(newNode); mAdapter.addNode(newNode);
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) { if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode); //mAdapter.updateLastNodeRegister(newNode);
mAdapter.rebuildList(mCurrentGroup); mAdapter.rebuildList(currentGroup);
} }
} else { } else {
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result"); Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
@@ -267,7 +281,7 @@ public class ListNodesFragment extends StylishFragment implements
} }
public PwGroup getMainGroup() { public PwGroup getMainGroup() {
return mCurrentGroup; return currentGroup;
} }
public interface OnScrollListener { public interface OnScrollListener {

View File

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

View File

@@ -29,6 +29,7 @@ abstract class BasicViewHolder extends RecyclerView.ViewHolder {
View container; View container;
ImageView icon; ImageView icon;
TextView text; TextView text;
TextView subText;
BasicViewHolder(View itemView) { BasicViewHolder(View itemView) {
super(itemView); super(itemView);

View File

@@ -30,5 +30,6 @@ class EntryViewHolder extends BasicViewHolder {
container = itemView.findViewById(R.id.entry_container); container = itemView.findViewById(R.id.entry_container);
icon = itemView.findViewById(R.id.entry_icon); icon = itemView.findViewById(R.id.entry_icon);
text = itemView.findViewById(R.id.entry_text); text = itemView.findViewById(R.id.entry_text);
subText = itemView.findViewById(R.id.entry_subtext);
} }
} }

View File

@@ -30,5 +30,6 @@ class GroupViewHolder extends BasicViewHolder {
container = itemView.findViewById(R.id.group_container); container = itemView.findViewById(R.id.group_container);
icon = itemView.findViewById(R.id.group_icon); icon = itemView.findViewById(R.id.group_icon);
text = itemView.findViewById(R.id.group_text); text = itemView.findViewById(R.id.group_text);
subText = itemView.findViewById(R.id.group_subtext);
} }
} }

View File

@@ -26,6 +26,7 @@ import android.support.annotation.NonNull;
import android.support.v7.util.SortedList; import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.util.SortedListAdapterCallback; import android.support.v7.widget.util.SortedListAdapterCallback;
import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -33,16 +34,20 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup; import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwNode; import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum; import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.settings.PreferencesUtil; import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.utils.Util;
public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> { public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private static final String TAG = NodeAdapter.class.getName();
private SortedList<PwNode> nodeSortedList; private SortedList<PwNode> nodeSortedList;
@@ -50,14 +55,20 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private LayoutInflater inflater; private LayoutInflater inflater;
private MenuInflater menuInflater; private MenuInflater menuInflater;
private float textSize; private float textSize;
private float subtextSize;
private float iconSize; private float iconSize;
private SortNodeEnum listSort; private SortNodeEnum listSort;
private boolean groupsBeforeSort; private boolean groupsBeforeSort;
private boolean ascendingSort; private boolean ascendingSort;
private boolean showUsernames;
private NodeClickCallback nodeClickCallback; private NodeClickCallback nodeClickCallback;
private NodeMenuListener nodeMenuListener; private NodeMenuListener nodeMenuListener;
private boolean activateContextMenu; private boolean activateContextMenu;
private boolean readOnly;
private boolean isASearchResult;
private Database database;
private int iconGroupColor; private int iconGroupColor;
private int iconEntryColor; private int iconEntryColor;
@@ -72,6 +83,8 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
this.context = context; this.context = context;
assignPreferences(); assignPreferences();
this.activateContextMenu = false; this.activateContextMenu = false;
this.readOnly = false;
this.isASearchResult = false;
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) { this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
@Override public int compare(PwNode item1, PwNode item2) { @Override public int compare(PwNode item1, PwNode item2) {
@@ -87,6 +100,9 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
} }
}); });
// Database
this.database = App.getDB();
// Retrieve the color to tint the icon // Retrieve the color to tint the icon
int[] attrTextColorPrimary = {android.R.attr.textColorPrimary}; int[] attrTextColorPrimary = {android.R.attr.textColorPrimary};
TypedArray taTextColorPrimary = context.getTheme().obtainStyledAttributes(attrTextColorPrimary); TypedArray taTextColorPrimary = context.getTheme().obtainStyledAttributes(attrTextColorPrimary);
@@ -98,22 +114,30 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
taTextColor.recycle(); taTextColor.recycle();
} }
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
public void setIsASearchResult(boolean isASearchResult) {
this.isASearchResult = isASearchResult;
}
public void setActivateContextMenu(boolean activate) { public void setActivateContextMenu(boolean activate) {
this.activateContextMenu = activate; this.activateContextMenu = activate;
} }
private void assignPreferences() { private void assignPreferences() {
float textSizeDefault = Util.getListTextDefaultSize(context);
this.textSize = PreferencesUtil.getListTextSize(context); this.textSize = PreferencesUtil.getListTextSize(context);
this.subtextSize = context.getResources().getInteger(R.integer.list_small_size_default)
* textSize / textSizeDefault;
// Retrieve the icon size // Retrieve the icon size
int iconDefaultSize = (int) TypedValue.applyDimension( float iconDefaultSize = context.getResources().getDimension(R.dimen.list_icon_size_default);
TypedValue.COMPLEX_UNIT_DIP, this.iconSize = iconDefaultSize * textSize / textSizeDefault;
context.getResources().getInteger(R.integer.list_icon_size_default),
context.getResources().getDisplayMetrics()
);
this.iconSize = iconDefaultSize * textSize / Float.parseFloat(context.getString(R.string.list_size_default));
this.listSort = PreferencesUtil.getListSort(context); this.listSort = PreferencesUtil.getListSort(context);
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context); this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
this.ascendingSort = PreferencesUtil.getAscendingSort(context); this.ascendingSort = PreferencesUtil.getAscendingSort(context);
this.showUsernames = PreferencesUtil.showUsernamesListEntries(context);
} }
/** /**
@@ -122,11 +146,23 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
public void rebuildList(PwGroup group) { public void rebuildList(PwGroup group) {
this.nodeSortedList.clear(); this.nodeSortedList.clear();
assignPreferences(); assignPreferences();
if (group != null) { // TODO verify sort
try {
this.nodeSortedList.addAll(group.getDirectChildren()); this.nodeSortedList.addAll(group.getDirectChildren());
} catch (Exception e) {
Log.e(TAG, "Can't add node elements to the list", e);
Toast.makeText(context, "Can't add node elements to the list : " + e.getMessage(), Toast.LENGTH_LONG).show();
} }
} }
/**
* Determine if the adapter contains or not any element
* @return true if the list is empty
*/
public boolean isEmpty() {
return nodeSortedList.size() <= 0;
}
/** /**
* Add a node to the list * Add a node to the list
* @param node Node to add * @param node Node to add
@@ -188,35 +224,51 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
public void onBindViewHolder(@NonNull BasicViewHolder holder, int position) { public void onBindViewHolder(@NonNull BasicViewHolder holder, int position) {
PwNode subNode = nodeSortedList.get(position); PwNode subNode = nodeSortedList.get(position);
// Assign image // Assign image
if (IconPackChooser.getSelectedIconPack(context).tintable()) { int iconColor = Color.BLACK;
int iconColor = Color.BLACK; switch (subNode.getType()) {
switch (subNode.getType()) { case GROUP:
case GROUP: iconColor = iconGroupColor;
iconColor = iconGroupColor; break;
break; case ENTRY:
case ENTRY: iconColor = iconEntryColor;
iconColor = iconEntryColor; break;
break;
}
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon());
} }
database.getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon(), iconColor);
// Assign text // Assign text
holder.text.setText(subNode.getDisplayTitle()); holder.text.setText(subNode.getTitle());
// Assign click // Assign click
holder.container.setOnClickListener( holder.container.setOnClickListener(
new OnNodeClickListener(subNode)); new OnNodeClickListener(subNode));
// Context menu // Context menu
if (activateContextMenu) { if (activateContextMenu) {
holder.container.setOnCreateContextMenuListener( holder.container.setOnCreateContextMenuListener(
new ContextMenuBuilder(subNode, nodeMenuListener)); new ContextMenuBuilder(subNode, nodeMenuListener, readOnly));
} }
// Add username
holder.subText.setText("");
holder.subText.setVisibility(View.GONE);
if (subNode.getType().equals(PwNode.Type.ENTRY)) {
PwEntry entry = (PwEntry) subNode;
entry.startToManageFieldReferences(database.getPwDatabase());
holder.text.setText(entry.getVisualTitle());
String username = entry.getUsername();
if (showUsernames && !username.isEmpty()) {
holder.subText.setVisibility(View.VISIBLE);
holder.subText.setText(username);
}
entry.stopToManageFieldReferences();
}
// Assign image and text size // Assign image and text size
// Relative size of the icon // Relative size of the icon
holder.icon.getLayoutParams().height = ((int) iconSize); holder.icon.getLayoutParams().height = ((int) iconSize);
holder.icon.getLayoutParams().width = ((int) iconSize); holder.icon.getLayoutParams().width = ((int) iconSize);
holder.text.setTextSize(textSize); holder.text.setTextSize(textSize);
holder.subText.setTextSize(subtextSize);
} }
@Override @Override
@@ -280,36 +332,56 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private PwNode node; private PwNode node;
private NodeMenuListener menuListener; private NodeMenuListener menuListener;
private boolean readOnly;
ContextMenuBuilder(PwNode node, NodeMenuListener menuListener) { ContextMenuBuilder(PwNode node, NodeMenuListener menuListener, boolean readOnly) {
this.menuListener = menuListener; this.menuListener = menuListener;
this.node = node; this.node = node;
this.readOnly = readOnly;
} }
@Override @Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
menuInflater.inflate(R.menu.node_menu, contextMenu); menuInflater.inflate(R.menu.node_menu, contextMenu);
// TODO COPY For Group // Opening
if (node.getType().equals(PwNode.Type.GROUP)) {
contextMenu.removeItem(R.id.menu_copy);
}
MenuItem menuItem = contextMenu.findItem(R.id.menu_open); MenuItem menuItem = contextMenu.findItem(R.id.menu_open);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener); menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
if (!App.getDB().isReadOnly() && !node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
// Edition // Edition
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_edit);
} else {
menuItem = contextMenu.findItem(R.id.menu_edit); menuItem = contextMenu.findItem(R.id.menu_edit);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener); menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Copy (not for group) }
if (node.getType().equals(PwNode.Type.ENTRY)) {
menuItem = contextMenu.findItem(R.id.menu_copy); // Copy (not for group)
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener); if (readOnly
} || isASearchResult
// Move || node.equals(App.getDB().getPwDatabase().getRecycleBin())
|| node.getType().equals(PwNode.Type.GROUP)) {
// TODO COPY For Group
contextMenu.removeItem(R.id.menu_copy);
} else {
menuItem = contextMenu.findItem(R.id.menu_copy);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}
// Move
if (readOnly
|| isASearchResult
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_move);
} else {
menuItem = contextMenu.findItem(R.id.menu_move); menuItem = contextMenu.findItem(R.id.menu_move);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener); menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Deletion }
// Deletion
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_delete);
} else {
menuItem = contextMenu.findItem(R.id.menu_delete); menuItem = contextMenu.findItem(R.id.menu_delete);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener); menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
} }

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwIconFactory;
import com.kunzisoft.keepass.database.cursor.EntryCursor;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import java.util.UUID;
public class SearchEntryCursorAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
private Database database;
private boolean displayUsername;
private int iconColor;
public SearchEntryCursorAdapter(Context context, Database database) {
super(context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
cursorInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
this.database = database;
// Get the icon color
int[] attrTextColor = {R.attr.textColorInverse};
TypedArray taTextColor = context.getTheme().obtainStyledAttributes(attrTextColor);
this.iconColor = taTextColor.getColor(0, Color.WHITE);
taTextColor.recycle();
reInit(context);
}
public void reInit(Context context) {
this.displayUsername = PreferencesUtil.showUsernamesListEntries(context);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = cursorInflater.inflate(R.layout.search_entry, parent ,false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon);
viewHolder.textViewTitle = view.findViewById(R.id.entry_text);
viewHolder.textViewSubTitle = view.findViewById(R.id.entry_subtext);
view.setTag(viewHolder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// Retrieve elements from cursor
UUID uuid = new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)));
PwIconFactory iconFactory = database.getPwDatabase().getIconFactory();
PwIcon icon = iconFactory.getIcon(
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
if (icon.isUnknown()) {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
if (icon.isUnknown())
icon = iconFactory.getKeyIcon();
}
String title = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE) );
String username = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME) );
String url = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL) );
ViewHolder viewHolder = (ViewHolder) view.getTag();
// Assign image
database.getDrawFactory().assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon, iconColor);
// Assign title
String showTitle = PwEntry.getVisualTitle(false, title, username, url, uuid);
viewHolder.textViewTitle.setText(showTitle);
if (displayUsername && !username.isEmpty()) {
viewHolder.textViewSubTitle.setText(String.format("(%s)", username));
} else {
viewHolder.textViewSubTitle.setText("");
}
}
private static class ViewHolder {
ImageView imageViewIcon;
TextView textViewTitle;
TextView textViewSubTitle;
}
@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return database.searchEntry(constraint.toString());
}
public PwEntry getEntryFromPosition(int position) {
PwEntry pwEntry = null;
Cursor cursor = this.getCursor();
if (cursor.moveToFirst()
&&
cursor.move(position)) {
pwEntry = database.createEntry();
database.populateEntry(pwEntry, (EntryCursor) cursor);
}
return pwEntry;
}
}

View File

@@ -92,7 +92,7 @@ public class App extends MultiDexApplication {
@Override @Override
public void onTerminate() { public void onTerminate() {
if ( db != null ) { if ( db != null ) {
db.clear(); db.clear(getApplicationContext());
} }
super.onTerminate(); super.onTerminate();
} }

View File

@@ -19,20 +19,57 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import java.io.Serializable; import android.os.Parcel;
import android.os.Parcelable;
import com.kunzisoft.keepass.utils.MemUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
public class AutoType implements Cloneable, Serializable { public class AutoType implements Cloneable, Parcelable {
private static final long OBF_OPT_NONE = 0; private static final long OBF_OPT_NONE = 0;
public boolean enabled = true; public boolean enabled = true;
public long obfuscationOptions = OBF_OPT_NONE; public long obfuscationOptions = OBF_OPT_NONE;
public String defaultSequence = ""; public String defaultSequence = "";
private HashMap<String, String> windowSeqPairs = new HashMap<>(); private HashMap<String, String> windowSeqPairs = new HashMap<>();
public AutoType() {}
public AutoType(Parcel in) {
enabled = in.readByte() != 0;
obfuscationOptions = in.readLong();
defaultSequence = in.readString();
windowSeqPairs = MemUtil.readStringParcelableMap(in);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (enabled ? 1 : 0));
dest.writeLong(obfuscationOptions);
dest.writeString(defaultSequence);
MemUtil.writeStringParcelableMap(dest, windowSeqPairs);
}
public static final Parcelable.Creator<AutoType> CREATOR = new Parcelable.Creator<AutoType>() {
@Override
public AutoType createFromParcel(Parcel in) {
return new AutoType(in);
}
@Override
public AutoType[] newArray(int size) {
return new AutoType[size];
}
};
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public AutoType clone() { public AutoType clone() {
AutoType auto; AutoType auto;

View File

@@ -51,6 +51,8 @@ public class BinaryPool {
} }
public void clear() { public void clear() {
for (Entry<Integer, ProtectedBinary> entry: pool.entrySet())
entry.getValue().clear();
pool.clear(); pool.clear();
} }
@@ -63,30 +65,36 @@ public class BinaryPool {
@Override @Override
public boolean operate(PwEntryV4 entry) { public boolean operate(PwEntryV4 entry) {
for (PwEntryV4 histEntry : entry.getHistory()) { for (PwEntryV4 histEntry : entry.getHistory()) {
poolAdd(histEntry.getBinaries()); add(histEntry.getBinaries());
} }
poolAdd(entry.getBinaries()); add(entry.getBinaries());
return true; return true;
} }
} }
private void poolAdd(Map<String, ProtectedBinary> dict) { private void add(Map<String, ProtectedBinary> dict) {
for (ProtectedBinary pb : dict.values()) { for (ProtectedBinary pb : dict.values()) {
poolAdd(pb); add(pb);
} }
} }
public void poolAdd(ProtectedBinary pb) { public void add(ProtectedBinary pb) {
assert(pb != null); assert(pb != null);
if (findKey(pb) != -1) return;
if (poolFind(pb) != -1) return; pool.put(findUnusedKey(), pb);
pool.put(pool.size(), pb);
} }
public int findUnusedKey() {
int unusedKey = pool.size();
while(get(unusedKey) != null)
unusedKey++;
return unusedKey;
}
public int poolFind(ProtectedBinary pb) { public int findKey(ProtectedBinary pb) {
for (Entry<Integer, ProtectedBinary> pair : pool.entrySet()) { for (Entry<Integer, ProtectedBinary> pair : pool.entrySet()) {
if (pair.getValue().equals(pb)) return pair.getKey(); if (pair.getValue().equals(pb)) return pair.getKey();
} }

View File

@@ -21,22 +21,26 @@ package com.kunzisoft.keepass.database;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine; import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory; import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
import com.kunzisoft.keepass.database.cursor.EntryCursor;
import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException; import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException;
import com.kunzisoft.keepass.database.exception.InvalidDBException; import com.kunzisoft.keepass.database.exception.InvalidDBException;
import com.kunzisoft.keepass.database.exception.PwDbOutputException; import com.kunzisoft.keepass.database.exception.PwDbOutputException;
import com.kunzisoft.keepass.database.load.Importer; import com.kunzisoft.keepass.database.load.Importer;
import com.kunzisoft.keepass.database.load.ImporterFactory; import com.kunzisoft.keepass.database.load.ImporterFactory;
import com.kunzisoft.keepass.database.save.PwDbOutput; import com.kunzisoft.keepass.database.save.PwDbOutput;
import com.kunzisoft.keepass.database.search.SearchDbHelper;
import com.kunzisoft.keepass.icons.IconDrawableFactory; import com.kunzisoft.keepass.icons.IconDrawableFactory;
import com.kunzisoft.keepass.search.SearchDbHelper;
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater; import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
import com.kunzisoft.keepass.utils.UriUtil; import com.kunzisoft.keepass.utils.UriUtil;
import org.apache.commons.io.FileUtils;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -56,7 +60,7 @@ public class Database {
private static final String TAG = Database.class.getName(); private static final String TAG = Database.class.getName();
private PwDatabase pm; private PwDatabase pwDatabase;
private Uri mUri; private Uri mUri;
private SearchDbHelper searchHelper; private SearchDbHelper searchHelper;
private boolean readOnly = false; private boolean readOnly = false;
@@ -67,11 +71,11 @@ public class Database {
private boolean loaded = false; private boolean loaded = false;
public PwDatabase getPwDatabase() { public PwDatabase getPwDatabase() {
return pm; return pwDatabase;
} }
public void setPwDatabase(PwDatabase pm) { public void setPwDatabase(PwDatabase pm) {
this.pm = pm; this.pwDatabase = pm;
} }
public void setUri(Uri mUri) { public void setUri(Uri mUri) {
@@ -149,24 +153,25 @@ public class Database {
// We'll end up reading 8 bytes to identify the header. Might as well use two extra. // We'll end up reading 8 bytes to identify the header. Might as well use two extra.
bis.mark(10); bis.mark(10);
Importer databaseImporter = ImporterFactory.createImporter(bis, debug); // Get the file directory to save the attachments
Importer databaseImporter = ImporterFactory.createImporter(bis, ctx.getFilesDir(), debug);
bis.reset(); // Return to the start bis.reset(); // Return to the start
pm = databaseImporter.openDatabase(bis, password, keyFileInputStream, progressTaskUpdater); pwDatabase = databaseImporter.openDatabase(bis, password, keyFileInputStream, progressTaskUpdater);
if ( pm != null ) { if ( pwDatabase != null ) {
try { try {
switch (pm.getVersion()) { switch (pwDatabase.getVersion()) {
case V3: case V3:
PwGroupV3 rootV3 = ((PwDatabaseV3) pm).getRootGroup(); PwGroupV3 rootV3 = ((PwDatabaseV3) pwDatabase).getRootGroup();
((PwDatabaseV3) pm).populateGlobals(rootV3); ((PwDatabaseV3) pwDatabase).populateGlobals(rootV3);
passwordEncodingError = !pm.validatePasswordEncoding(password); passwordEncodingError = !pwDatabase.validatePasswordEncoding(password);
searchHelper = new SearchDbHelper.SearchDbHelperV3(ctx); searchHelper = new SearchDbHelper.SearchDbHelperV3(ctx);
break; break;
case V4: case V4:
PwGroupV4 rootV4 = ((PwDatabaseV4) pm).getRootGroup(); PwGroupV4 rootV4 = ((PwDatabaseV4) pwDatabase).getRootGroup();
((PwDatabaseV4) pm).populateGlobals(rootV4); ((PwDatabaseV4) pwDatabase).populateGlobals(rootV4);
passwordEncodingError = !pm.validatePasswordEncoding(password); passwordEncodingError = !pwDatabase.validatePasswordEncoding(password);
searchHelper = new SearchDbHelper.SearchDbHelperV4(ctx); searchHelper = new SearchDbHelper.SearchDbHelperV4(ctx);
break; break;
} }
@@ -179,13 +184,17 @@ public class Database {
} }
public PwGroup search(String str) { public PwGroup search(String str) {
return search(str, Integer.MAX_VALUE);
}
public PwGroup search(String str, int max) {
if (searchHelper == null) { return null; } if (searchHelper == null) { return null; }
try { try {
switch (pm.getVersion()) { switch (pwDatabase.getVersion()) {
case V3: case V3:
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str); return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pwDatabase), str, max);
case V4: case V4:
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str); return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pwDatabase), str, max);
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Search can't be performed with this SearchHelper", e); Log.e(TAG, "Search can't be performed with this SearchHelper", e);
@@ -193,6 +202,54 @@ public class Database {
return null; return null;
} }
public Cursor searchEntry(String query) {
final EntryCursor cursor = new EntryCursor();
// TODO real content provider
if (!query.isEmpty()) {
PwGroup searchResult = search(query, 6);
PwVersion version = getPwDatabase().getVersion();
if (searchResult != null) {
for (int i = 0; i < searchResult.numbersOfChildEntries(); i++) {
PwEntry entry = searchResult.getChildEntryAt(i);
if (!entry.isMetaStream()) { // TODO metastream
try {
switch (version) {
case V3:
cursor.addEntry((PwEntryV3) entry);
continue;
case V4:
cursor.addEntry((PwEntryV4) entry);
}
} catch (Exception e) {
Log.e(TAG, "Can't add PwEntry to the cursor", e);
}
}
}
}
}
return cursor;
}
public void populateEntry(PwEntry pwEntry, EntryCursor cursor) {
PwIconFactory iconFactory = getPwDatabase().getIconFactory();
try {
switch (getPwDatabase().getVersion()) {
case V3:
cursor.populateEntry((PwEntryV3) pwEntry, iconFactory);
break;
case V4:
// TODO invert field reference manager
pwEntry.startToManageFieldReferences(getPwDatabase());
cursor.populateEntry((PwEntryV4) pwEntry, iconFactory);
pwEntry.stopToManageFieldReferences();
break;
}
} catch (Exception e) {
Log.e(TAG, "This version of PwGroup can't be populated", e);
}
}
public void saveData(Context ctx) throws IOException, PwDbOutputException { public void saveData(Context ctx) throws IOException, PwDbOutputException {
saveData(ctx, mUri); saveData(ctx, mUri);
} }
@@ -207,7 +264,7 @@ public class Database {
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
fos = new FileOutputStream(tempFile); fos = new FileOutputStream(tempFile);
PwDbOutput pmo = PwDbOutput.getInstance(pm, fos); PwDbOutput pmo = PwDbOutput.getInstance(pwDatabase, fos);
if (pmo != null) if (pmo != null)
pmo.output(); pmo.output();
} catch (Exception e) { } catch (Exception e) {
@@ -235,7 +292,7 @@ public class Database {
OutputStream os = null; OutputStream os = null;
try { try {
os = ctx.getContentResolver().openOutputStream(uri); os = ctx.getContentResolver().openOutputStream(uri);
PwDbOutput pmo = PwDbOutput.getInstance(pm, os); PwDbOutput pmo = PwDbOutput.getInstance(pwDatabase, os);
if (pmo != null) if (pmo != null)
pmo.output(); pmo.output();
} catch (Exception e) { } catch (Exception e) {
@@ -250,10 +307,19 @@ public class Database {
} }
// TODO Clear database when lock broadcast is receive in backstage // TODO Clear database when lock broadcast is receive in backstage
public void clear() { public void clear(Context context) {
drawFactory.clearCache(); drawFactory.clearCache();
// Delete the cache of the database if present
if (pwDatabase != null)
pwDatabase.clearCache();
// In all cases, delete all the files in the temp dir
try {
FileUtils.cleanDirectory(context.getFilesDir());
} catch (IOException e) {
Log.e(TAG, "Unable to clear the directory cache.", e);
}
pm = null; pwDatabase = null;
mUri = null; mUri = null;
loaded = false; loaded = false;
passwordEncodingError = false; passwordEncodingError = false;
@@ -464,19 +530,22 @@ public class Database {
} }
} }
public PwEntry createEntry(PwGroup parent) { public PwEntry createEntry() {
PwEntry newPwEntry = null; return createEntry(null);
}
public PwEntry createEntry(@Nullable PwGroup parent) {
try { try {
switch (getPwDatabase().getVersion()) { switch (getPwDatabase().getVersion()) {
case V3: case V3:
newPwEntry = new PwEntryV3((PwGroupV3) parent); return new PwEntryV3((PwGroupV3) parent);
case V4: case V4:
newPwEntry = new PwEntryV4((PwGroupV4) parent); return new PwEntryV4((PwGroupV4) parent);
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "This version of PwEntry can't be created", e); Log.e(TAG, "This version of PwEntry can't be created", e);
} }
return newPwEntry; return null;
} }
public PwGroup createGroup(PwGroup parent) { public PwGroup createGroup(PwGroup parent) {
@@ -488,7 +557,7 @@ public class Database {
case V4: case V4:
newPwGroup = new PwGroupV4((PwGroupV4) parent); newPwGroup = new PwGroupV4((PwGroupV4) parent);
} }
newPwGroup.setId(pm.newGroupId()); newPwGroup.setId(pwDatabase.newGroupId());
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "This version of PwGroup can't be created", e); Log.e(TAG, "This version of PwGroup can't be created", e);
} }

View File

@@ -19,9 +19,12 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import com.kunzisoft.keepass.database.security.ProtectedString; import android.os.Parcel;
import android.os.Parcelable;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.utils.MemUtil;
import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@@ -32,7 +35,7 @@ import static com.kunzisoft.keepass.database.PwEntryV4.STR_TITLE;
import static com.kunzisoft.keepass.database.PwEntryV4.STR_URL; import static com.kunzisoft.keepass.database.PwEntryV4.STR_URL;
import static com.kunzisoft.keepass.database.PwEntryV4.STR_USERNAME; import static com.kunzisoft.keepass.database.PwEntryV4.STR_USERNAME;
public class ExtraFields implements Serializable, Cloneable { public class ExtraFields implements Parcelable, Cloneable {
private Map<String, ProtectedString> fields; private Map<String, ProtectedString> fields;
@@ -40,6 +43,32 @@ public class ExtraFields implements Serializable, Cloneable {
fields = new HashMap<>(); fields = new HashMap<>();
} }
public ExtraFields(Parcel in) {
fields = MemUtil.readStringParcelableMap(in, ProtectedString.class);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
MemUtil.writeStringParcelableMap(dest, flags, fields);
}
public static final Parcelable.Creator<ExtraFields> CREATOR = new Parcelable.Creator<ExtraFields>() {
@Override
public ExtraFields createFromParcel(Parcel in) {
return new ExtraFields(in);
}
@Override
public ExtraFields[] newArray(int size) {
return new ExtraFields[size];
}
};
public boolean containsCustomFields() { public boolean containsCustomFields() {
return !getCustomProtectedFields().keySet().isEmpty(); return !getCustomProtectedFields().keySet().isEmpty();
} }

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database;
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException; import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
import com.kunzisoft.keepass.database.exception.KeyFileEmptyException; import com.kunzisoft.keepass.database.exception.KeyFileEmptyException;
import com.kunzisoft.keepass.utils.Util; import com.kunzisoft.keepass.utils.MemUtil;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -35,7 +35,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB, PwEntryDB>, public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwEntryDB>,
PwEntryDB extends PwEntry<PwGroupDB>> { PwEntryDB extends PwEntry<PwGroupDB>> {
public static final UUID UUID_ZERO = new UUID(0,0); public static final UUID UUID_ZERO = new UUID(0,0);
@@ -128,7 +128,7 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
assert(keyInputStream != null); assert(keyInputStream != null);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
Util.copyStream(keyInputStream, bos); MemUtil.copyStream(keyInputStream, bos);
byte[] keyData = bos.toByteArray(); byte[] keyData = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(keyData); ByteArrayInputStream bis = new ByteArrayInputStream(keyData);
@@ -430,4 +430,6 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
*/ */
public abstract void initNew(String dbPath); public abstract void initNew(String dbPath);
public abstract void clearCache();
} }

View File

@@ -91,7 +91,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
PwGroupV3 group = createGroup(); PwGroupV3 group = createGroup();
group.setId(newGroupId()); group.setId(newGroupId());
group.setName(name); group.setName(name);
group.setIcon(iconFactory.getIcon(iconId)); group.setIconStandard(iconFactory.getIcon(iconId));
addGroupTo(group, parent); addGroupTo(group, parent);
} }
@@ -176,7 +176,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
*/ */
for (int i = 0; i < entries.size(); i++) { for (int i = 0; i < entries.size(); i++) {
PwEntryV3 ent = entries.get(i); PwEntryV3 ent = entries.get(i);
if (ent.getGroupId() == parent.getGroupId()) if (ent.getParent().getGroupId() == parent.getGroupId())
kids.add(ent); kids.add(ent);
} }
return kids; return kids;
@@ -376,4 +376,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
return !(omitBackup && isBackup(group)); return !(omitBackup && isBackup(group));
} }
@Override
public void clearCache() {}
} }

View File

@@ -754,4 +754,8 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
return filename.substring(0, lastExtDot); return filename.substring(0, lastExtDot);
} }
@Override
public void clearCache() {
binPool.clear();
}
} }

View File

@@ -19,10 +19,12 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import android.os.Parcelable;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.utils.Types; import com.kunzisoft.keepass.utils.Types;
import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@@ -33,15 +35,14 @@ import java.util.Date;
* @author bpellin * @author bpellin
* *
*/ */
public class PwDate implements Cloneable, Serializable { public class PwDate implements Cloneable, Parcelable {
private static final int DATE_SIZE = 5; private static final int DATE_SIZE = 5;
private boolean cDateBuilt = false; private Date jDate;
private boolean jDateBuilt = false; private boolean jDateBuilt = false;
transient private byte[] cDate;
private Date jDate; transient private boolean cDateBuilt = false;
private byte[] cDate;
public static final Date NEVER_EXPIRE = getNeverExpire(); public static final Date NEVER_EXPIRE = getNeverExpire();
public static final Date DEFAULT_DATE = getDefaultDate(); public static final Date DEFAULT_DATE = getDefaultDate();
@@ -93,6 +94,35 @@ public class PwDate implements Cloneable, Serializable {
jDate = new Date(); jDate = new Date();
jDateBuilt = true; jDateBuilt = true;
} }
protected PwDate(Parcel in) {
jDate = (Date) in.readSerializable();
jDateBuilt = in.readByte() != 0;
cDateBuilt = false;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(getDate());
dest.writeByte((byte) (jDateBuilt ? 1 : 0));
}
public static final Creator<PwDate> CREATOR = new Creator<PwDate>() {
@Override
public PwDate createFromParcel(Parcel in) {
return new PwDate(in);
}
@Override
public PwDate[] newArray(int size) {
return new PwDate[size];
}
};
@Override @Override
public PwDate clone() { public PwDate clone() {

View File

@@ -19,6 +19,8 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator; import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
import com.kunzisoft.keepass.database.security.ProtectedString; import com.kunzisoft.keepass.database.security.ProtectedString;
@@ -30,6 +32,19 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
protected UUID uuid = PwDatabase.UUID_ZERO; protected UUID uuid = PwDatabase.UUID_ZERO;
public PwEntry() {}
public PwEntry(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeSerializable(uuid);
}
@Override @Override
protected void construct(Parent parent) { protected void construct(Parent parent) {
super.construct(parent); super.construct(parent);
@@ -61,7 +76,7 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
} }
public void startToManageFieldReferences(PwDatabase db) {} public void startToManageFieldReferences(PwDatabase db) {}
public void endToManageFieldReferences() {} public void stopToManageFieldReferences() {}
public abstract String getTitle(); public abstract String getTitle();
public abstract void setTitle(String title); public abstract void setTitle(String title);
@@ -82,15 +97,31 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0); return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0);
} }
@Override /**
public String getDisplayTitle() { * {@inheritDoc}
if ( isTan() ) { * Get the display title from an entry, <br />
return PMS_TAN_ENTRY + " " + getUsername(); * {@link #startToManageFieldReferences(PwDatabase)} and {@link #stopToManageFieldReferences()} must be called
} else { * before and after {@link #getVisualTitle()}
return getTitle(); */
} public String getVisualTitle() {
// only used to compare, don't car if it's a reference
return getVisualTitle(isTan(), getTitle(), getUsername(), getUrl(), getUUID());
} }
public static String getVisualTitle(boolean isTAN, String title, String username, String url, UUID uuid) {
if ( isTAN ) {
return PMS_TAN_ENTRY + " " + username;
} else {
if (title.isEmpty())
if (url.isEmpty())
return uuid.toString();
else
return url;
else
return title;
}
}
// TODO encapsulate extra fields // TODO encapsulate extra fields
/** /**

View File

@@ -42,6 +42,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.UUID; import java.util.UUID;
@@ -75,15 +77,11 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
private static final String PMS_ID_USER = "SYSTEM"; private static final String PMS_ID_USER = "SYSTEM";
private static final String PMS_ID_URL = "$"; private static final String PMS_ID_URL = "$";
// TODO Parent ID to remove
private int groupId;
private String title; private String title;
private String username; private String username;
private byte[] password; private byte[] password;
private String url; private String url;
private String additional; private String additional;
/** A string describing what is in pBinaryData */ /** A string describing what is in pBinaryData */
private String binaryDesc; private String binaryDesc;
private byte[] binaryData; private byte[] binaryData;
@@ -94,12 +92,45 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
public PwEntryV3(PwGroupV3 p) { public PwEntryV3(PwGroupV3 p) {
construct(p); construct(p);
groupId = ((PwGroupIdV3) this.parent.getId()).getId(); // TODO remove
} }
public PwEntryV3(Parcel in) {
super(in);
title = in.readString();
username = in.readString();
in.readByteArray(password);
url = in.readString();
additional = in.readString();
binaryDesc = in.readString();
in.readByteArray(binaryData);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(title);
dest.writeString(username);
dest.writeByteArray(password);
dest.writeString(url);
dest.writeString(additional);
dest.writeString(binaryDesc);
dest.writeByteArray(binaryData);
}
public static final Creator<PwEntryV3> CREATOR = new Creator<PwEntryV3>() {
@Override
public PwEntryV3 createFromParcel(Parcel in) {
return new PwEntryV3(in);
}
@Override
public PwEntryV3[] newArray(int size) {
return new PwEntryV3[size];
}
};
protected void updateWith(PwEntryV3 source) { protected void updateWith(PwEntryV3 source) {
super.assign(source); super.assign(source);
groupId = source.groupId;
title = source.title; title = source.title;
username = source.username; username = source.username;
@@ -145,12 +176,9 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
return newEntry; return newEntry;
} }
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) { public void setGroupId(int groupId) {
this.groupId = groupId; this.parent = new PwGroupV3();
this.parent.setGroupId(groupId);
} }
@Override @Override

View File

@@ -19,8 +19,11 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import com.kunzisoft.keepass.database.security.ProtectedBinary; import com.kunzisoft.keepass.database.security.ProtectedBinary;
import com.kunzisoft.keepass.database.security.ProtectedString; import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.utils.MemUtil;
import com.kunzisoft.keepass.utils.SprEngineV4; import com.kunzisoft.keepass.utils.SprEngineV4;
import java.util.ArrayList; import java.util.ArrayList;
@@ -37,7 +40,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
public static final String STR_URL = "URL"; public static final String STR_URL = "URL";
public static final String STR_NOTES = "Notes"; public static final String STR_NOTES = "Notes";
// To decode each field not serializable // To decode each field not parcelable
private transient PwDatabaseV4 mDatabase = null; private transient PwDatabaseV4 mDatabase = null;
private transient boolean mDecodeRef = false; private transient boolean mDecodeRef = false;
@@ -45,7 +48,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
private long usageCount = 0; private long usageCount = 0;
private PwDate parentGroupLastMod = new PwDate(); private PwDate parentGroupLastMod = new PwDate();
private Map<String, String> customData = new HashMap<>(); private Map<String, String> customData = new HashMap<>();
private ExtraFields fields = new ExtraFields(); private ExtraFields fields = new ExtraFields();
private HashMap<String, ProtectedBinary> binaries = new HashMap<>(); private HashMap<String, ProtectedBinary> binaries = new HashMap<>();
private String foregroundColor = ""; private String foregroundColor = "";
@@ -53,7 +55,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
private String overrideURL = ""; private String overrideURL = "";
private AutoType autoType = new AutoType(); private AutoType autoType = new AutoType();
private ArrayList<PwEntryV4> history = new ArrayList<>(); private ArrayList<PwEntryV4> history = new ArrayList<>();
private String url = ""; private String url = "";
private String additional = ""; private String additional = "";
private String tags = ""; private String tags = "";
@@ -71,8 +72,8 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
customIcon = source.customIcon; customIcon = source.customIcon;
usageCount = source.usageCount; usageCount = source.usageCount;
parentGroupLastMod = source.parentGroupLastMod; parentGroupLastMod = source.parentGroupLastMod;
// TODO customData customData.clear();
customData.putAll(source.customData); // Add all custom elements in map
fields = source.fields; fields = source.fields;
binaries = source.binaries; binaries = source.binaries;
foregroundColor = source.foregroundColor; foregroundColor = source.foregroundColor;
@@ -80,12 +81,60 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
overrideURL = source.overrideURL; overrideURL = source.overrideURL;
autoType = source.autoType; autoType = source.autoType;
history = source.history; history = source.history;
url = source.url; url = source.url;
additional = source.additional; additional = source.additional;
tags = source.tags; tags = source.tags;
} }
public PwEntryV4(Parcel in) {
super(in);
customIcon = in.readParcelable(PwIconCustom.class.getClassLoader());
usageCount = in.readLong();
parentGroupLastMod = in.readParcelable(PwDate.class.getClassLoader());
customData = MemUtil.readStringParcelableMap(in);
fields = in.readParcelable(ExtraFields.class.getClassLoader());
binaries = MemUtil.readStringParcelableMap(in, ProtectedBinary.class);
foregroundColor = in.readString();
backgroupColor = in.readString();
overrideURL = in.readString();
autoType = in.readParcelable(AutoType.class.getClassLoader());
history = in.readArrayList(PwEntryV4.class.getClassLoader()); // TODO verify
url = in.readString();
additional = in.readString();
tags = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(customIcon, flags);
dest.writeLong(usageCount);
dest.writeParcelable(parentGroupLastMod, flags);
MemUtil.writeStringParcelableMap(dest, customData);
dest.writeParcelable(fields, flags);
// TODO MemUtil.writeStringParcelableMap(dest, flags, binaries);
dest.writeString(foregroundColor);
dest.writeString(backgroupColor);
dest.writeString(overrideURL);
dest.writeParcelable(autoType, flags);
dest.writeList(history);
dest.writeString(url);
dest.writeString(additional);
dest.writeString(tags);
}
public static final Creator<PwEntryV4> CREATOR = new Creator<PwEntryV4>() {
@Override
public PwEntryV4 createFromParcel(Parcel in) {
return new PwEntryV4(in);
}
@Override
public PwEntryV4[] newArray(int size) {
return new PwEntryV4[size];
}
};
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public PwEntryV4 clone() { public PwEntryV4 clone() {
@@ -120,7 +169,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
} }
@Override @Override
public void endToManageFieldReferences() { public void stopToManageFieldReferences() {
this.mDatabase = null; this.mDatabase = null;
this.mDecodeRef = false; this.mDecodeRef = false;
} }
@@ -156,41 +205,31 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
@Override @Override
public void setTitle(String title) { public void setTitle(String title) {
PwDatabaseV4 db = mDatabase; boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectTitle;
boolean protect = db.getMemoryProtection().protectTitle;
setProtectedString(STR_TITLE, title, protect); setProtectedString(STR_TITLE, title, protect);
} }
@Override @Override
public void setUsername(String user) { public void setUsername(String user) {
PwDatabaseV4 db = mDatabase; boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUserName;
boolean protect = db.getMemoryProtection().protectUserName;
setProtectedString(STR_USERNAME, user, protect); setProtectedString(STR_USERNAME, user, protect);
} }
@Override @Override
public void setPassword(String pass) { public void setPassword(String pass) {
PwDatabaseV4 db = mDatabase; boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectPassword;
boolean protect = db.getMemoryProtection().protectPassword;
setProtectedString(STR_PASSWORD, pass, protect); setProtectedString(STR_PASSWORD, pass, protect);
} }
@Override @Override
public void setUrl(String url) { public void setUrl(String url) {
PwDatabaseV4 db = mDatabase; boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUrl;
boolean protect = db.getMemoryProtection().protectUrl;
setProtectedString(STR_URL, url, protect); setProtectedString(STR_URL, url, protect);
} }
@Override @Override
public void setNotes(String notes) { public void setNotes(String notes) {
PwDatabaseV4 db = mDatabase; boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectNotes;
boolean protect = db.getMemoryProtection().protectNotes;
setProtectedString(STR_NOTES, notes, protect); setProtectedString(STR_NOTES, notes, protect);
} }
@@ -202,14 +241,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
fields.putProtectedString(key, value, protect); fields.putProtectedString(key, value, protect);
} }
public PwIconCustom getCustomIcon() {
return customIcon;
}
public void setCustomIcon(PwIconCustom icon) {
this.customIcon = icon;
}
public PwDate getLocationChanged() { public PwDate getLocationChanged() {
return parentGroupLastMod; return parentGroupLastMod;
} }
@@ -236,15 +267,28 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
return decodeRefKey(mDecodeRef, STR_URL); return decodeRefKey(mDecodeRef, STR_URL);
} }
@Override @Override
public PwIcon getIcon() { public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) { if (customIcon == null || customIcon.isUnknown()) {
return super.getIcon(); return super.getIcon();
} else { } else {
return customIcon; return customIcon;
} }
} }
public void setIconCustom(PwIconCustom icon) {
this.customIcon = icon;
}
public PwIconCustom getIconCustom() {
return customIcon;
}
public void setIconStandard(PwIconStandard icon) {
this.icon = icon;
this.customIcon = PwIconCustom.ZERO;
}
@Override @Override
public boolean allowExtraFields() { public boolean allowExtraFields() {
return true; return true;

View File

@@ -19,16 +19,34 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup, ChildEntry extends PwEntry> public abstract class PwGroup<GroupG extends PwGroup, EntryE extends PwEntry>
extends PwNode<Parent> { extends PwNode<GroupG> {
protected String name = ""; protected String name = "";
protected List<ChildGroup> childGroups = new ArrayList<>(); // TODO verify children not needed
protected List<ChildEntry> childEntries = new ArrayList<>(); transient protected List<GroupG> childGroups = new ArrayList<>();
transient protected List<EntryE> childEntries = new ArrayList<>();
protected PwGroup() {
super();
}
protected PwGroup(Parcel in) {
super(in);
name = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(name);
}
@Override @Override
public PwGroup clone() { public PwGroup clone() {
@@ -36,48 +54,48 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
return (PwGroup) super.clone(); return (PwGroup) super.clone();
} }
protected void assign(PwGroup<Parent, ChildGroup, ChildEntry> source) { protected void assign(PwGroup<GroupG, EntryE> source) {
super.assign(source); super.assign(source);
name = source.name; name = source.name;
} }
public List<ChildGroup> getChildGroups() { public List<GroupG> getChildGroups() {
return childGroups; return childGroups;
} }
public List<ChildEntry> getChildEntries() { public List<EntryE> getChildEntries() {
return childEntries; return childEntries;
} }
public void setGroups(List<ChildGroup> groups) { public void setGroups(List<GroupG> groups) {
childGroups = groups; childGroups = groups;
} }
public void setEntries(List<ChildEntry> entries) { public void setEntries(List<EntryE> entries) {
childEntries = entries; childEntries = entries;
} }
public void addChildGroup(ChildGroup group) { public void addChildGroup(GroupG group) {
this.childGroups.add(group); this.childGroups.add(group);
} }
public void addChildEntry(ChildEntry entry) { public void addChildEntry(EntryE entry) {
this.childEntries.add(entry); this.childEntries.add(entry);
} }
public ChildGroup getChildGroupAt(int number) { public GroupG getChildGroupAt(int number) {
return this.childGroups.get(number); return this.childGroups.get(number);
} }
public ChildEntry getChildEntryAt(int number) { public EntryE getChildEntryAt(int number) {
return this.childEntries.get(number); return this.childEntries.get(number);
} }
public void removeChildGroup(ChildGroup group) { public void removeChildGroup(GroupG group) {
this.childGroups.remove(group); this.childGroups.remove(group);
} }
public void removeChildEntry(ChildEntry entry) { public void removeChildEntry(EntryE entry) {
this.childEntries.remove(entry); this.childEntries.remove(entry);
} }
@@ -101,7 +119,7 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
public List<PwNode> getDirectChildren() { public List<PwNode> getDirectChildren() {
List<PwNode> children = new ArrayList<>(); List<PwNode> children = new ArrayList<>();
children.addAll(childGroups); children.addAll(childGroups);
for(ChildEntry child : childEntries) { for(EntryE child : childEntries) {
if (!child.isMetaStream()) if (!child.isMetaStream())
children.add(child); children.add(child);
} }
@@ -112,10 +130,18 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
public abstract void setId(PwGroupId id); public abstract void setId(PwGroupId id);
@Override @Override
public String getDisplayTitle() { protected String getVisualTitle() {
return getTitle();
}
@Override
public String getTitle() {
return getName(); return getName();
} }
/**
* The same thing as {@link #getTitle()}
*/
public String getName() { public String getName() {
return name; return name;
} }
@@ -128,15 +154,15 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
return false; return false;
} }
public boolean preOrderTraverseTree(GroupHandler<ChildGroup> groupHandler, public boolean preOrderTraverseTree(GroupHandler<GroupG> groupHandler,
EntryHandler<ChildEntry> entryHandler) { EntryHandler<EntryE> entryHandler) {
if (entryHandler != null) { if (entryHandler != null) {
for (ChildEntry entry : childEntries) { for (EntryE entry : childEntries) {
if (!entryHandler.operate(entry)) return false; if (!entryHandler.operate(entry)) return false;
} }
} }
for (ChildGroup group : childGroups) { for (GroupG group : childGroups) {
if ((groupHandler != null) && !groupHandler.operate(group)) return false; if ((groupHandler != null) && !groupHandler.operate(group)) return false;
group.preOrderTraverseTree(groupHandler, entryHandler); group.preOrderTraverseTree(groupHandler, entryHandler);
} }

View File

@@ -19,8 +19,20 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import java.io.Serializable; import android.os.Parcel;
import android.os.Parcelable;
public abstract class PwGroupId implements Serializable { public abstract class PwGroupId implements Parcelable {
public PwGroupId() {}
public PwGroupId(Parcel in) {}
@Override
public void writeToParcel(Parcel dest, int flags) {}
@Override
public int describeContents() {
return 0;
}
} }

View File

@@ -19,13 +19,39 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
public class PwGroupIdV3 extends PwGroupId { public class PwGroupIdV3 extends PwGroupId {
private int id; private int id;
public PwGroupIdV3(int i) { public PwGroupIdV3(int groupId) {
id = i; super();
this.id = groupId;
} }
public PwGroupIdV3(Parcel in) {
super(in);
id = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(id);
}
public static final Creator<PwGroupIdV3> CREATOR = new Creator<PwGroupIdV3>() {
@Override
public PwGroupIdV3 createFromParcel(Parcel in) {
return new PwGroupIdV3(in);
}
@Override
public PwGroupIdV3[] newArray(int size) {
return new PwGroupIdV3[size];
}
};
@Override @Override
public boolean equals(Object compare) { public boolean equals(Object compare) {

View File

@@ -19,15 +19,42 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.util.UUID; import java.util.UUID;
public class PwGroupIdV4 extends PwGroupId { public class PwGroupIdV4 extends PwGroupId {
private UUID uuid; private UUID uuid;
public PwGroupIdV4(UUID u) { public PwGroupIdV4(UUID uuid) {
uuid = u; super();
this.uuid = uuid;
} }
public PwGroupIdV4(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeSerializable(uuid);
}
public static final Creator<PwGroupIdV4> CREATOR = new Creator<PwGroupIdV4>() {
@Override
public PwGroupIdV4 createFromParcel(Parcel in) {
return new PwGroupIdV4(in);
}
@Override
public PwGroupIdV4[] newArray(int size) {
return new PwGroupIdV4[size];
}
};
@Override @Override
public boolean equals(Object id) { public boolean equals(Object id) {
if ( ! (id instanceof PwGroupIdV4) ) { if ( ! (id instanceof PwGroupIdV4) ) {
@@ -36,12 +63,12 @@ public class PwGroupIdV4 extends PwGroupId {
PwGroupIdV4 v4 = (PwGroupIdV4) id; PwGroupIdV4 v4 = (PwGroupIdV4) id;
return uuid.equals(v4.uuid); return uuid.equals(v4.uuid);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return uuid.hashCode(); return uuid.hashCode();
} }
public UUID getId() { public UUID getId() {
return uuid; return uuid;
} }

View File

@@ -20,19 +20,13 @@
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
/** import android.os.Parcel;
* @author Brian Pellin <bpellin@gmail.com>
* @author Naomaru Itoi <nao@phoneid.org> public class PwGroupV3 extends PwGroup<PwGroupV3, PwEntryV3> {
* @author Bill Zwicky <wrzwicky@pobox.com>
* @author Dominik Reichl <dominik.reichl@t-online.de>
*/
public class PwGroupV3 extends PwGroup<PwGroupV3, PwGroupV3, PwEntryV3> {
// for tree traversing // for tree traversing
private int groupId; private int groupId;
private int level = 0; // short private int level = 0; // short
/** Used by KeePass internally, don't use */ /** Used by KeePass internally, don't use */
private int flags; private int flags;
@@ -40,6 +34,33 @@ public class PwGroupV3 extends PwGroup<PwGroupV3, PwGroupV3, PwEntryV3> {
super(); super();
} }
public PwGroupV3(Parcel in) {
super(in);
groupId = in.readInt();
level = in.readInt();
flags = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(groupId);
dest.writeInt(level);
dest.writeInt(flags);
}
public static final Creator<PwGroupV3> CREATOR = new Creator<PwGroupV3>() {
@Override
public PwGroupV3 createFromParcel(Parcel in) {
return new PwGroupV3(in);
}
@Override
public PwGroupV3[] newArray(int size) {
return new PwGroupV3[size];
}
};
public PwGroupV3(PwGroupV3 p) { public PwGroupV3(PwGroupV3 p) {
construct(p); construct(p);
} }

View File

@@ -19,11 +19,15 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import com.kunzisoft.keepass.utils.MemUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implements ITimeLogger { public class PwGroupV4 extends PwGroup<PwGroupV4, PwEntryV4> implements ITimeLogger {
public static final boolean DEFAULT_SEARCHING_ENABLED = true; public static final boolean DEFAULT_SEARCHING_ENABLED = true;
@@ -32,9 +36,7 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
private long usageCount = 0; private long usageCount = 0;
private PwDate parentGroupLastMod = new PwDate(); private PwDate parentGroupLastMod = new PwDate();
private Map<String, String> customData = new HashMap<>(); private Map<String, String> customData = new HashMap<>();
private boolean expires = false; private boolean expires = false;
private String notes = ""; private String notes = "";
private boolean isExpanded = true; private boolean isExpanded = true;
private String defaultAutoTypeSequence = ""; private String defaultAutoTypeSequence = "";
@@ -57,6 +59,53 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
this.icon = icon; this.icon = icon;
} }
public PwGroupV4(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
customIcon = in.readParcelable(PwIconCustom.class.getClassLoader());
usageCount = in.readLong();
parentGroupLastMod = in.readParcelable(PwDate.class.getClassLoader());
// TODO customData = MemUtil.readStringParcelableMap(in);
expires = in.readByte() != 0;
notes = in.readString();
isExpanded = in.readByte() != 0;
defaultAutoTypeSequence = in.readString();
byte autoTypeByte = in.readByte();
enableAutoType = (autoTypeByte == -1) ? null : autoTypeByte != 0;
byte enableSearchingByte = in.readByte();
enableSearching = (enableSearchingByte == -1) ? null : enableSearchingByte != 0;
lastTopVisibleEntry = (UUID) in.readSerializable();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeSerializable(uuid);
dest.writeParcelable(customIcon, flags);
dest.writeLong(usageCount);
dest.writeParcelable(parentGroupLastMod, flags);
// TODO MemUtil.writeStringParcelableMap(dest, customData);
dest.writeByte((byte) (expires ? 1 : 0));
dest.writeString(notes);
dest.writeByte((byte) (isExpanded ? 1 : 0));
dest.writeString(defaultAutoTypeSequence);
dest.writeByte((byte) (enableAutoType == null ? -1 : (enableAutoType ? 1 : 0)));
dest.writeByte((byte) (enableAutoType == null ? -1 : (enableAutoType ? 1 : 0)));
dest.writeSerializable(lastTopVisibleEntry);
}
public static final Creator<PwGroupV4> CREATOR = new Creator<PwGroupV4>() {
@Override
public PwGroupV4 createFromParcel(Parcel in) {
return new PwGroupV4(in);
}
@Override
public PwGroupV4[] newArray(int size) {
return new PwGroupV4[size];
}
};
protected void updateWith(PwGroupV4 source) { protected void updateWith(PwGroupV4 source) {
super.assign(source); super.assign(source);
uuid = source.uuid; uuid = source.uuid;
@@ -120,14 +169,6 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
this.uuid = uuid; this.uuid = uuid;
} }
public PwIconCustom getCustomIcon() {
return customIcon;
}
public void setCustomIcon(PwIconCustom icon) {
this.customIcon = icon;
}
@Override @Override
public PwGroupId getId() { public PwGroupId getId() {
return new PwGroupIdV4(uuid); return new PwGroupIdV4(uuid);
@@ -176,13 +217,26 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
@Override @Override
public PwIcon getIcon() { public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) { if (customIcon == null || customIcon.getUUID().equals(PwDatabase.UUID_ZERO)) {
return super.getIcon(); return super.getIcon();
} else { } else {
return customIcon; return customIcon;
} }
} }
public PwIconCustom getIconCustom() {
return customIcon;
}
public void setIconCustom(PwIconCustom icon) {
this.customIcon = icon;
}
public void setIconStandard(PwIconStandard icon) { // TODO Encapsulate with PwEntryV4
this.icon = icon;
this.customIcon = PwIconCustom.ZERO;
}
public void putCustomData(String key, String value) { public void putCustomData(String key, String value) {
customData.put(key, value); customData.put(key, value);
} }

View File

@@ -19,11 +19,23 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import java.io.Serializable; import android.os.Parcel;
import android.os.Parcelable;
public abstract class PwIcon implements Serializable { public abstract class PwIcon implements Parcelable {
public boolean isMetaStreamIcon() { public boolean isMetaStreamIcon() {
return false; return false;
} }
protected PwIcon() {}
protected PwIcon(Parcel in) {}
public abstract boolean isUnknown();
@Override
public int describeContents() {
return 0;
}
} }

View File

@@ -19,25 +19,71 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.util.UUID; import java.util.UUID;
public class PwIconCustom extends PwIcon { public class PwIconCustom extends PwIcon {
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]); public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
public final UUID uuid; private final UUID uuid;
public byte[] imageData; transient private byte[] imageData;
public PwIconCustom(UUID u, byte[] data) { public PwIconCustom(UUID uuid, byte[] data) {
uuid = u; super();
imageData = data; this.uuid = uuid;
this.imageData = data;
} }
public PwIconCustom(PwIconCustom icon) { public PwIconCustom(PwIconCustom icon) {
super();
uuid = icon.uuid; uuid = icon.uuid;
imageData = icon.imageData; imageData = icon.imageData;
} }
protected PwIconCustom(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
// TODO Take too much memories
// in.readByteArray(imageData);
}
@Override @Override
public boolean isUnknown() {
return uuid == null || this.equals(ZERO);
}
public UUID getUUID() {
return uuid;
}
public byte[] getImageData() {
return imageData;
}
public void setImageData(byte[] imageData) {
this.imageData = imageData;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(uuid);
// Too big for a parcelable dest.writeByteArray(imageData);
}
public static final Creator<PwIconCustom> CREATOR = new Creator<PwIconCustom>() {
@Override
public PwIconCustom createFromParcel(Parcel in) {
return new PwIconCustom(in);
}
@Override
public PwIconCustom[] newArray(int size) {
return new PwIconCustom[size];
}
};
@Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
@@ -55,10 +101,7 @@ public class PwIconCustom extends PwIcon {
return false; return false;
PwIconCustom other = (PwIconCustom) obj; PwIconCustom other = (PwIconCustom) obj;
if (uuid == null) { if (uuid == null) {
if (other.uuid != null) return other.uuid == null;
return false; } else return uuid.equals(other.uuid);
} else if (!uuid.equals(other.uuid))
return false;
return true;
} }
} }

View File

@@ -37,6 +37,10 @@ public class PwIconFactory {
*/ */
private ReferenceMap customCache = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK); private ReferenceMap customCache = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
public PwIconStandard getUnknownIcon() {
return getIcon(PwIconStandard.UNKNOWN);
}
public PwIconStandard getKeyIcon() { public PwIconStandard getKeyIcon() {
return getIcon(PwIconStandard.KEY); return getIcon(PwIconStandard.KEY);
} }
@@ -46,8 +50,8 @@ public class PwIconFactory {
} }
public PwIconStandard getFolderIcon() { public PwIconStandard getFolderIcon() {
return getIcon(PwIconStandard.FOLDER); return getIcon(PwIconStandard.FOLDER);
} }
public PwIconStandard getIcon(int iconId) { public PwIconStandard getIcon(int iconId) {
PwIconStandard icon = (PwIconStandard) cache.get(iconId); PwIconStandard icon = (PwIconStandard) cache.get(iconId);
@@ -71,25 +75,8 @@ public class PwIconFactory {
return icon; return icon;
} }
public PwIconCustom getIcon(UUID iconUuid, byte[] data) {
PwIconCustom icon = (PwIconCustom) customCache.get(iconUuid);
if (icon == null) {
icon = new PwIconCustom(iconUuid, data);
customCache.put(iconUuid, icon);
} else {
icon.imageData = data;
}
return icon;
}
public void setIconData(UUID iconUuid, byte[] data) {
getIcon(iconUuid, data);
}
public void put(PwIconCustom icon) { public void put(PwIconCustom icon) {
customCache.put(icon.uuid, icon); customCache.put(icon.getUUID(), icon);
} }
} }

View File

@@ -19,13 +19,20 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
public class PwIconStandard extends PwIcon { import android.os.Parcel;
public final int iconId;
public class PwIconStandard extends PwIcon {
private final int iconId;
public static final int UNKNOWN = -1;
public static final int KEY = 0; public static final int KEY = 0;
public static final int TRASH = 43; public static final int TRASH = 43;
public static final int FOLDER = 48; public static final int FOLDER = 48;
public PwIconStandard() {
this.iconId = KEY;
}
public PwIconStandard(int iconId) { public PwIconStandard(int iconId) {
this.iconId = iconId; this.iconId = iconId;
} }
@@ -34,6 +41,37 @@ public class PwIconStandard extends PwIcon {
this.iconId = icon.iconId; this.iconId = icon.iconId;
} }
protected PwIconStandard(Parcel in) {
super(in);
iconId = in.readInt();
}
@Override
public boolean isUnknown() {
return iconId == UNKNOWN;
}
public int getIconId() {
return iconId;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(iconId);
}
public static final Creator<PwIconStandard> CREATOR = new Creator<PwIconStandard>() {
@Override
public PwIconStandard createFromParcel(Parcel in) {
return new PwIconStandard(in);
}
@Override
public PwIconStandard[] newArray(int size) {
return new PwIconStandard[size];
}
};
@Override @Override
public boolean isMetaStreamIcon() { public boolean isMetaStreamIcon() {
return iconId == 0; return iconId == 0;

View File

@@ -20,33 +20,69 @@
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database;
import org.joda.time.LocalDate; import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable; import com.kunzisoft.keepass.app.App;
import org.joda.time.LocalDate;
/** /**
* Abstract class who manage Groups and Entries * Abstract class who manage Groups and Entries
*/ */
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Serializable, Cloneable { public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Parcelable, Cloneable {
protected Parent parent = null; protected Parent parent = null;
protected PwIconStandard icon = new PwIconStandard();
protected PwIconStandard icon = new PwIconStandard(0);
protected PwDate creation = new PwDate(); protected PwDate creation = new PwDate();
protected PwDate lastMod = new PwDate(); protected PwDate lastMod = new PwDate();
protected PwDate lastAccess = new PwDate(); protected PwDate lastAccess = new PwDate();
protected PwDate expireDate = PwDate.PW_NEVER_EXPIRE; protected PwDate expireDate = PwDate.PW_NEVER_EXPIRE;
protected PwNode() {}
protected PwNode(Parcel in) {
// TODO better technique ?
try {
PwGroupId pwGroupId = in.readParcelable(PwGroupId.class.getClassLoader());
parent = (Parent) App.getDB().getPwDatabase().getGroupByGroupId(pwGroupId);
} catch (Exception e) {
e.printStackTrace();
}
icon = in.readParcelable(PwIconStandard.class.getClassLoader());
creation = in.readParcelable(PwDate.class.getClassLoader());
lastMod = in.readParcelable(PwDate.class.getClassLoader());
lastAccess = in.readParcelable(PwDate.class.getClassLoader());
expireDate = in.readParcelable(PwDate.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
PwGroupId parentId = null;
if (parent != null)
parentId = parent.getId();
dest.writeParcelable(parentId, flags);
dest.writeParcelable(icon, flags);
dest.writeParcelable(creation, flags);
dest.writeParcelable(lastMod, flags);
dest.writeParcelable(lastAccess, flags);
dest.writeParcelable(expireDate, flags);
}
@Override
public int describeContents() {
return 0;
}
protected void construct(Parent parent) { protected void construct(Parent parent) {
this.parent = parent; this.parent = parent;
} }
protected void assign(PwNode<Parent> source) { protected void assign(PwNode<Parent> source) {
this.parent = source.parent; this.parent = source.parent;
this.icon = source.icon; this.icon = source.icon;
this.creation = source.creation; this.creation = source.creation;
this.lastMod = source.lastMod; this.lastMod = source.lastMod;
this.lastAccess = source.lastAccess; this.lastAccess = source.lastAccess;
@@ -59,9 +95,7 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
try { try {
newNode = (PwNode) super.clone(); newNode = (PwNode) super.clone();
// newNode.parent stay the same in copy // newNode.parent stay the same in copy
newNode.icon = new PwIconStandard(this.icon); newNode.icon = new PwIconStandard(this.icon);
newNode.creation = creation.clone(); newNode.creation = creation.clone();
newNode.lastMod = lastMod.clone(); newNode.lastMod = lastMod.clone();
newNode.lastAccess = lastAccess.clone(); newNode.lastAccess = lastAccess.clone();
@@ -85,22 +119,27 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
public abstract Type getType(); public abstract Type getType();
/** /**
* @return Title to display as view * @return Title
*/ */
public abstract String getDisplayTitle(); public abstract String getTitle();
/**
* @return Title to display, typically return alternative title if {@link #getTitle()} is empty
*/
protected abstract String getVisualTitle();
/** /**
* @return Visual icon * @return Visual icon
*/ */
public PwIcon getIcon() { public PwIcon getIcon() {
return icon; return getIconStandard();
} }
public PwIconStandard getIconStandard() { public PwIconStandard getIconStandard() {
return icon; return icon;
} }
public void setIcon(PwIconStandard icon) { public void setIconStandard(PwIconStandard icon) {
this.icon = icon; this.icon = icon;
} }
@@ -176,7 +215,7 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
*/ */
public boolean isContentVisuallyTheSame(PwNode o) { public boolean isContentVisuallyTheSame(PwNode o) {
return getType().equals(o.getType()) return getType().equals(o.getType())
&& getDisplayTitle().equals(o.getDisplayTitle()) && getVisualTitle().equals(o.getVisualTitle())
&& getIcon().equals(o.getIcon()); && getIcon().equals(o.getIcon());
} }

View File

@@ -109,8 +109,8 @@ public enum SortNodeEnum {
new EntryNameComparator(ascending), new EntryNameComparator(ascending),
object1, object1,
object2, object2,
object1.getDisplayTitle() object1.getTitle()
.compareToIgnoreCase(object2.getDisplayTitle())); .compareToIgnoreCase(object2.getTitle()));
} }
} }

View File

@@ -69,14 +69,12 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
public void run() { public void run() {
try { try {
mDatabase.loadData(mContext, mUri, mPass, mKey, mStatus); mDatabase.loadData(mContext, mUri, mPass, mKey, mStatus);
saveFileData(mUri, mKey); saveFileData(mUri, mKey);
} catch (ArcFourException e) { } catch (ArcFourException e) {
catchError(e, R.string.error_arc4); catchError(e, R.string.error_arc4);
return; return;
} catch (InvalidPasswordException e) { } catch (InvalidPasswordException e) {
catchError(e, R.string.InvalidPassword); catchError(e, R.string.invalid_password);
return; return;
} catch (ContentFileNotFoundException e) { } catch (ContentFileNotFoundException e) {
catchError(e, R.string.file_not_found_content); catchError(e, R.string.file_not_found_content);
@@ -85,8 +83,10 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
catchError(e, R.string.file_not_found); catchError(e, R.string.file_not_found);
return; return;
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Database can't be read", e); if (e.getMessage().contains("Hash failed with code"))
finish(false, e.getMessage()); catchError(e, R.string.error_load_database_KDF_memory, true);
else
catchError(e, R.string.error_load_database, true);
return; return;
} catch (KeyFileEmptyException e) { } catch (KeyFileEmptyException e) {
catchError(e, R.string.keyfile_is_empty); catchError(e, R.string.keyfile_is_empty);
@@ -107,22 +107,24 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
catchError(e, R.string.error_invalid_db); catchError(e, R.string.error_invalid_db);
return; return;
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
String errorMessage = mContext.getString(R.string.error_out_of_memory); catchError(e, R.string.error_out_of_memory);
Log.e(TAG, errorMessage, e);
finish(false, errorMessage);
return; return;
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Database can't be load", e); catchError(e, R.string.error_load_database, true);
finish(false, e.getMessage());
return; return;
} }
finish(true); finish(true);
} }
private void catchError(Exception e, @StringRes int messageId) { private void catchError(Throwable e, @StringRes int messageId) {
catchError(e, messageId, false);
}
private void catchError(Throwable e, @StringRes int messageId, boolean addThrowableMessage) {
String errorMessage = mContext.getString(messageId); String errorMessage = mContext.getString(messageId);
Log.e(TAG, errorMessage, e); Log.e(TAG, errorMessage, e);
if (addThrowableMessage)
errorMessage = errorMessage + " " + e.getLocalizedMessage();
finish(false, errorMessage); finish(false, errorMessage);
} }

View File

@@ -31,15 +31,15 @@ import java.util.List;
public class DeleteGroupRunnable extends ActionNodeDatabaseRunnable { public class DeleteGroupRunnable extends ActionNodeDatabaseRunnable {
private PwGroup<PwGroup, PwGroup, PwEntry> mGroupToDelete; private PwGroup<PwGroup, PwEntry> mGroupToDelete;
private PwGroup mParent; private PwGroup mParent;
private boolean mRecycle; private boolean mRecycle;
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish) { public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwEntry> group, AfterActionNodeOnFinish finish) {
this(ctx, db, group, finish, false); this(ctx, db, group, finish, false);
} }
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish, boolean dontSave) { public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwEntry> group, AfterActionNodeOnFinish finish, boolean dontSave) {
super(ctx, db, finish, dontSave); super(ctx, db, finish, dontSave);
mGroupToDelete = group; mGroupToDelete = group;
} }

View File

@@ -0,0 +1,125 @@
package com.kunzisoft.keepass.database.cursor;
import android.database.MatrixCursor;
import android.provider.BaseColumns;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwIconCustom;
import com.kunzisoft.keepass.database.PwIconFactory;
import com.kunzisoft.keepass.database.PwIconStandard;
import java.util.UUID;
public class EntryCursor extends MatrixCursor {
private long entryId;
public static final String _ID = BaseColumns._ID;
public static final String COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUID_most_significant_bits";
public static final String COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUID_least_significant_bits";
public static final String COLUMN_INDEX_TITLE = "title";
public static final String COLUMN_INDEX_ICON_STANDARD = "icon_standard";
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS = "icon_custom_UUID_most_significant_bits";
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS = "icon_custom_UUID_least_significant_bits";
public static final String COLUMN_INDEX_USERNAME = "username";
public static final String COLUMN_INDEX_PASSWORD = "password";
public static final String COLUMN_INDEX_URL = "URL";
public static final String COLUMN_INDEX_NOTES = "notes";
private ExtraFieldCursor extraFieldCursor;
public EntryCursor() {
super(new String[]{ _ID,
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
COLUMN_INDEX_TITLE,
COLUMN_INDEX_ICON_STANDARD,
COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS,
COLUMN_INDEX_USERNAME,
COLUMN_INDEX_PASSWORD,
COLUMN_INDEX_URL,
COLUMN_INDEX_NOTES});
entryId = 0;
extraFieldCursor = new ExtraFieldCursor();
}
public void addEntry(PwEntryV3 entry) {
addRow(new Object[] {entryId,
entry.getUUID().getMostSignificantBits(),
entry.getUUID().getLeastSignificantBits(),
entry.getTitle(),
entry.getIconStandard().getIconId(),
PwDatabase.UUID_ZERO.getMostSignificantBits(),
PwDatabase.UUID_ZERO.getLeastSignificantBits(),
entry.getUsername(),
entry.getPassword(),
entry.getUrl(),
entry.getNotes()});
entryId++;
}
public void addEntry(PwEntryV4 entry) {
addRow(new Object[] {entryId,
entry.getUUID().getMostSignificantBits(),
entry.getUUID().getLeastSignificantBits(),
entry.getTitle(),
entry.getIconStandard().getIconId(),
entry.getIconCustom().getUUID().getMostSignificantBits(),
entry.getIconCustom().getUUID().getLeastSignificantBits(),
entry.getUsername(),
entry.getPassword(),
entry.getUrl(),
entry.getNotes()});
entry.getFields().doActionToAllCustomProtectedField((key, value) -> {
extraFieldCursor.addExtraField(entryId, key, value);
});
entryId++;
}
private void populateEntryBaseVersion(PwEntry pwEntry, PwIconFactory iconFactory) {
pwEntry.setUUID(
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))));
pwEntry.setTitle(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE)));
PwIconStandard iconStandard = iconFactory.getIcon(getInt(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
pwEntry.setIconStandard(iconStandard);
pwEntry.setUsername(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME)));
pwEntry.setPassword(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_PASSWORD)));
pwEntry.setUrl(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_URL)));
pwEntry.setNotes(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_NOTES)));
}
public void populateEntry(PwEntryV3 pwEntry, PwIconFactory iconFactory) {
populateEntryBaseVersion(pwEntry, iconFactory);
}
public void populateEntry(PwEntryV4 pwEntry, PwIconFactory iconFactory) {
populateEntryBaseVersion(pwEntry, iconFactory);
// Retrieve custom icon
PwIconCustom iconCustom = iconFactory.getIcon(
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
pwEntry.setIconCustom(iconCustom);
// Retrieve extra fields
if (extraFieldCursor.moveToFirst()) {
while (!extraFieldCursor.isAfterLast()) {
// Add a new extra field only if entryId is the one we want
if (extraFieldCursor.getLong(extraFieldCursor.getColumnIndex(ExtraFieldCursor.FOREIGN_KEY_ENTRY_ID))
== getLong(getColumnIndex(EntryCursor._ID))) {
extraFieldCursor.populateExtraFieldInEntry(pwEntry);
}
extraFieldCursor.moveToNext();
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.kunzisoft.keepass.database.cursor;
import android.database.MatrixCursor;
import android.provider.BaseColumns;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.security.ProtectedString;
public class ExtraFieldCursor extends MatrixCursor {
private long fieldId;
public static final String _ID = BaseColumns._ID;
public static final String FOREIGN_KEY_ENTRY_ID = "entry_id";
public static final String COLUMN_LABEL = "label";
public static final String COLUMN_PROTECTION = "protection";
public static final String COLUMN_VALUE = "value";
public ExtraFieldCursor() {
super(new String[]{ _ID,
FOREIGN_KEY_ENTRY_ID,
COLUMN_LABEL,
COLUMN_PROTECTION,
COLUMN_VALUE});
fieldId = 0;
}
public synchronized void addExtraField(long entryId, String label, ProtectedString value) {
addRow(new Object[] {fieldId,
entryId,
label,
(value.isProtected()) ? 1 : 0,
value.toString()});
fieldId++;
}
public void populateExtraFieldInEntry(PwEntryV4 pwEntry) {
pwEntry.addExtraField(getString(getColumnIndex(ExtraFieldCursor.COLUMN_LABEL)),
new ProtectedString((getInt(getColumnIndex(ExtraFieldCursor.COLUMN_PROTECTION)) > 0),
getString(getColumnIndex(ExtraFieldCursor.COLUMN_VALUE))));
}
}

View File

@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntry; import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3; import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwEntryV4; import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.SearchParameters; import com.kunzisoft.keepass.database.search.SearchParameters;
import com.kunzisoft.keepass.database.SearchParametersV4; import com.kunzisoft.keepass.database.search.SearchParametersV4;
import java.util.Iterator; import java.util.Iterator;

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.iterator; package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntryV3; import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.SearchParameters; import com.kunzisoft.keepass.database.search.SearchParameters;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.iterator; package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntryV4; import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.SearchParametersV4; import com.kunzisoft.keepass.database.search.SearchParametersV4;
import com.kunzisoft.keepass.database.security.ProtectedString; import com.kunzisoft.keepass.database.security.ProtectedString;
import java.util.Iterator; import java.util.Iterator;
@@ -78,18 +78,19 @@ public class EntrySearchStringIteratorV4 extends EntrySearchStringIterator {
} }
private boolean searchInField(String key) { private boolean searchInField(String key) {
if (key.equals(PwEntryV4.STR_TITLE)) { switch (key) {
return sp.searchInTitles; case PwEntryV4.STR_TITLE:
} else if (key.equals(PwEntryV4.STR_USERNAME)) { return sp.searchInTitles;
return sp.searchInUserNames; case PwEntryV4.STR_USERNAME:
} else if (key.equals(PwEntryV4.STR_PASSWORD)) { return sp.searchInUserNames;
return sp.searchInPasswords; case PwEntryV4.STR_PASSWORD:
} else if (key.equals(PwEntryV4.STR_URL)) { return sp.searchInPasswords;
return sp.searchInUrls; case PwEntryV4.STR_URL:
} else if (key.equals(PwEntryV4.STR_NOTES)) { return sp.searchInUrls;
return sp.searchInNotes; case PwEntryV4.STR_NOTES:
} else { return sp.searchInNotes;
return sp.searchInOther; default:
return sp.searchInOther;
} }
} }

View File

@@ -24,15 +24,16 @@ import com.kunzisoft.keepass.database.PwDbHeaderV4;
import com.kunzisoft.keepass.database.exception.InvalidDBSignatureException; import com.kunzisoft.keepass.database.exception.InvalidDBSignatureException;
import com.kunzisoft.keepass.stream.LEDataInputStream; import com.kunzisoft.keepass.stream.LEDataInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
public class ImporterFactory { public class ImporterFactory {
public static Importer createImporter(InputStream is) throws InvalidDBSignatureException, IOException { public static Importer createImporter(InputStream is, File streamDir) throws InvalidDBSignatureException, IOException {
return createImporter(is, false); return createImporter(is, streamDir,false);
} }
public static Importer createImporter(InputStream is, boolean debug) throws InvalidDBSignatureException, IOException { public static Importer createImporter(InputStream is, File streamDir, boolean debug) throws InvalidDBSignatureException, IOException {
int sig1 = LEDataInputStream.readInt(is); int sig1 = LEDataInputStream.readInt(is);
int sig2 = LEDataInputStream.readInt(is); int sig2 = LEDataInputStream.readInt(is);
@@ -43,7 +44,7 @@ public class ImporterFactory {
return new ImporterV3(); return new ImporterV3();
} else if ( PwDbHeaderV4.matchesHeader(sig1, sig2) ) { } else if ( PwDbHeaderV4.matchesHeader(sig1, sig2) ) {
return new ImporterV4(); return new ImporterV4(streamDir);
} }
throw new InvalidDBSignatureException(); throw new InvalidDBSignatureException();

View File

@@ -156,7 +156,7 @@ public class ImporterV3 extends Importer {
} }
if (progressTaskUpdater != null) if (progressTaskUpdater != null)
progressTaskUpdater.updateMessage(R.string.creating_db_key); progressTaskUpdater.updateMessage(R.string.retrieving_db_key);
databaseToOpen = createDB(); databaseToOpen = createDB();
databaseToOpen.retrieveMasterKey(password, kfIs); databaseToOpen.retrieveMasterKey(password, kfIs);
@@ -316,7 +316,7 @@ public class ImporterV3 extends Importer {
grp.setExpiryTime(new PwDate(buf, offset)); grp.setExpiryTime(new PwDate(buf, offset));
break; break;
case 0x0007 : case 0x0007 :
grp.setIcon(db.getIconFactory().getIcon(LEDataInputStream.readInt(buf, offset))); grp.setIconStandard(db.getIconFactory().getIcon(LEDataInputStream.readInt(buf, offset)));
break; break;
case 0x0008 : case 0x0008 :
grp.setLevel(LEDataInputStream.readUShort(buf, offset)); grp.setLevel(LEDataInputStream.readUShort(buf, offset));
@@ -353,7 +353,7 @@ public class ImporterV3 extends Importer {
iconId = 0; iconId = 0;
} }
ent.setIcon(db.getIconFactory().getIcon(iconId)); ent.setIconStandard(db.getIconFactory().getIcon(iconId));
break; break;
case 0x0004 : case 0x0004 :
ent.setTitle(Types.readCString(buf, offset)); ent.setTitle(Types.readCString(buf, offset));

View File

@@ -54,6 +54,8 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlPullParserFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -62,10 +64,8 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Stack; import java.util.Stack;
import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@@ -80,12 +80,11 @@ public class ImporterV4 extends Importer {
private PwDatabaseV4 db; private PwDatabaseV4 db;
private byte[] hashOfHeader = null; private byte[] hashOfHeader = null;
private byte[] pbHeader = null;
private long version; private long version;
Calendar utcCal; private File streamDir;
public ImporterV4() { public ImporterV4(File streamDir) {
utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); this.streamDir = streamDir;
} }
@Override @Override
@@ -101,7 +100,7 @@ public class ImporterV4 extends Importer {
InvalidDBException { InvalidDBException {
if (progressTaskUpdater != null) if (progressTaskUpdater != null)
progressTaskUpdater.updateMessage(R.string.creating_db_key); progressTaskUpdater.updateMessage(R.string.retrieving_db_key);
db = new PwDatabaseV4(); db = new PwDatabaseV4();
PwDbHeaderV4 header = new PwDbHeaderV4(db); PwDbHeaderV4 header = new PwDbHeaderV4(db);
@@ -111,7 +110,7 @@ public class ImporterV4 extends Importer {
version = header.getVersion(); version = header.getVersion();
hashOfHeader = hh.hash; hashOfHeader = hh.hash;
pbHeader = hh.header; byte[] pbHeader = hh.header;
db.retrieveMasterKey(password, keyInputStream); db.retrieveMasterKey(password, keyInputStream);
db.makeFinalKey(header.masterSeed); db.makeFinalKey(header.masterSeed);
@@ -185,7 +184,6 @@ public class ImporterV4 extends Importer {
} }
if ( header.innerRandomStreamKey == null ) { if ( header.innerRandomStreamKey == null ) {
assert(false);
throw new IOException("Invalid stream key."); throw new IOException("Invalid stream key.");
} }
@@ -212,7 +210,10 @@ public class ImporterV4 extends Importer {
while(true) { while(true) {
if (!ReadInnerHeader(lis, header)) break; if (!ReadInnerHeader(lis, header)) break;
} }
}
private String getUnusedCacheFileName() {
return String.valueOf(db.getBinPool().findUnusedKey());
} }
private boolean ReadInnerHeader(LEDataInputStream lis, PwDbHeaderV4 header) throws IOException { private boolean ReadInnerHeader(LEDataInputStream lis, PwDbHeaderV4 header) throws IOException {
@@ -223,7 +224,8 @@ public class ImporterV4 extends Importer {
byte[] data = new byte[0]; byte[] data = new byte[0];
if (size > 0) { if (size > 0) {
data = lis.readBytes(size); if (fieldId != PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary)
data = lis.readBytes(size);
} }
boolean result = true; boolean result = true;
@@ -238,22 +240,20 @@ public class ImporterV4 extends Importer {
header.innerRandomStreamKey = data; header.innerRandomStreamKey = data;
break; break;
case PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary: case PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary:
if (data.length < 1) throw new IOException("Invalid binary format"); byte flag = lis.readBytes(1)[0];
byte flag = data[0]; boolean protectedFlag = (flag & PwDbHeaderV4.KdbxBinaryFlags.Protected) !=
boolean prot = (flag & PwDbHeaderV4.KdbxBinaryFlags.Protected) != PwDbHeaderV4.KdbxBinaryFlags.None;
PwDbHeaderV4.KdbxBinaryFlags.None; int byteLength = size - 1;
// Read in a file
byte[] bin = new byte[data.length - 1]; File file = new File(streamDir, getUnusedCacheFileName());
System.arraycopy(data, 1, bin, 0, data.length-1); try (FileOutputStream outputStream = new FileOutputStream(file)) {
ProtectedBinary pb = new ProtectedBinary(prot, bin); lis.readBytes(byteLength, outputStream::write);
db.getBinPool().poolAdd(pb);
if (prot) {
Arrays.fill(data, (byte)0);
} }
ProtectedBinary protectedBinary = new ProtectedBinary(protectedFlag, file, byteLength);
db.getBinPool().add(protectedBinary);
break; break;
default: default:
assert(false);
break; break;
} }
@@ -289,9 +289,9 @@ public class ImporterV4 extends Importer {
} }
private static final long DEFAULT_HISTORY_DAYS = 365; private static final long DEFAULT_HISTORY_DAYS = 365;
private boolean readNextNode = true; private boolean readNextNode = true;
private Stack<PwGroupV4> ctxGroups = new Stack<PwGroupV4>(); private Stack<PwGroupV4> ctxGroups = new Stack<>();
private PwGroupV4 ctxGroup = null; private PwGroupV4 ctxGroup = null;
private PwEntryV4 ctxEntry = null; private PwEntryV4 ctxEntry = null;
private String ctxStringName = null; private String ctxStringName = null;
@@ -337,7 +337,7 @@ public class ImporterV4 extends Importer {
ctxGroups.clear(); ctxGroups.clear();
KdbContext ctx = KdbContext.Null; KdbContext ctx = KdbContext.Null;
readNextNode = true; readNextNode = true;
while (true) { while (true) {
@@ -348,20 +348,17 @@ public class ImporterV4 extends Importer {
} }
switch ( xpp.getEventType() ) { switch ( xpp.getEventType() ) {
case XmlPullParser.START_TAG: case XmlPullParser.START_TAG:
ctx = ReadXmlElement(ctx, xpp); ctx = ReadXmlElement(ctx, xpp);
break; break;
case XmlPullParser.END_TAG:
ctx = EndXmlElement(ctx, xpp);
break;
default: case XmlPullParser.END_TAG:
assert(false); ctx = EndXmlElement(ctx, xpp);
break; break;
default:
break;
} }
} }
// Error checks // Error checks
@@ -533,8 +530,8 @@ public class ImporterV4 extends Importer {
case Root: case Root:
if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemGroup) ) { if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemGroup) ) {
assert(ctxGroups.size() == 0); if ( ctxGroups.size() != 0 )
if ( ctxGroups.size() != 0 ) throw new IOException("Group list should be empty."); throw new IOException("Group list should be empty.");
db.setRootGroup(new PwGroupV4()); db.setRootGroup(new PwGroupV4());
ctxGroups.push(db.getRootGroup()); ctxGroups.push(db.getRootGroup());
@@ -556,9 +553,9 @@ public class ImporterV4 extends Importer {
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemNotes) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemNotes) ) {
ctxGroup.setNotes(ReadString(xpp)); ctxGroup.setNotes(ReadString(xpp));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
ctxGroup.setIcon(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0))); ctxGroup.setIconStandard(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
ctxGroup.setCustomIcon(db.getIconFactory().getIcon(ReadUuid(xpp))); ctxGroup.setIconCustom(db.getIconFactory().getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemTimes) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemTimes) ) {
return SwitchContext(ctx, KdbContext.GroupTimes, xpp); return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIsExpanded) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIsExpanded) ) {
@@ -611,9 +608,9 @@ public class ImporterV4 extends Importer {
if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemUuid) ) { if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemUuid) ) {
ctxEntry.setUUID(ReadUuid(xpp)); ctxEntry.setUUID(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
ctxEntry.setIcon(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0))); ctxEntry.setIconStandard(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
ctxEntry.setCustomIcon(db.getIconFactory().getIcon(ReadUuid(xpp))); ctxEntry.setIconCustom(db.getIconFactory().getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemFgColor) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemFgColor) ) {
ctxEntry.setForegroundColor(ReadString(xpp)); ctxEntry.setForegroundColor(ReadString(xpp));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemBgColor) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemBgColor) ) {
@@ -633,8 +630,6 @@ public class ImporterV4 extends Importer {
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomData)) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomData)) {
return SwitchContext(ctx, KdbContext.EntryCustomData, xpp); return SwitchContext(ctx, KdbContext.EntryCustomData, xpp);
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemHistory) ) { } else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemHistory) ) {
assert(!entryInHistory);
if ( ! entryInHistory ) { if ( ! entryInHistory ) {
ctxHistoryBase = ctxEntry; ctxHistoryBase = ctxEntry;
return SwitchContext(ctx, KdbContext.EntryHistory, xpp); return SwitchContext(ctx, KdbContext.EntryHistory, xpp);
@@ -774,8 +769,8 @@ public class ImporterV4 extends Importer {
} }
private KdbContext EndXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException { private KdbContext EndXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException {
assert(xpp.getEventType() == XmlPullParser.END_TAG); // (xpp.getEventType() == XmlPullParser.END_TAG);
String name = xpp.getName(); String name = xpp.getName();
if ( ctx == KdbContext.KeePassFile && name.equalsIgnoreCase(PwDatabaseV4XML.ElemDocNode) ) { if ( ctx == KdbContext.KeePassFile && name.equalsIgnoreCase(PwDatabaseV4XML.ElemDocNode) ) {
return KdbContext.Null; return KdbContext.Null;
@@ -792,7 +787,7 @@ public class ImporterV4 extends Importer {
PwIconCustom icon = new PwIconCustom(customIconID, customIconData); PwIconCustom icon = new PwIconCustom(customIconID, customIconData);
db.addCustomIcon(icon); db.addCustomIcon(icon);
db.getIconFactory().put(icon); db.getIconFactory().put(icon);
} else assert(false); }
customIconID = PwDatabase.UUID_ZERO; customIconID = PwDatabase.UUID_ZERO;
customIconData = null; customIconData = null;
@@ -805,7 +800,7 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.CustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem) ) { } else if ( ctx == KdbContext.CustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem) ) {
if ( customDataKey != null && customDataValue != null) { if ( customDataKey != null && customDataValue != null) {
db.putCustomData(customDataKey, customDataValue); db.putCustomData(customDataKey, customDataValue);
} else assert(false); }
customDataKey = null; customDataKey = null;
customDataValue = null; customDataValue = null;
@@ -832,8 +827,6 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) { } else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) {
if (groupCustomDataKey != null && groupCustomDataValue != null) { if (groupCustomDataKey != null && groupCustomDataValue != null) {
ctxGroup.putCustomData(groupCustomDataKey, groupCustomDataKey); ctxGroup.putCustomData(groupCustomDataKey, groupCustomDataKey);
} else {
assert(false);
} }
groupCustomDataKey = null; groupCustomDataKey = null;
@@ -879,8 +872,6 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) { } else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) {
if (entryCustomDataKey != null && entryCustomDataValue != null) { if (entryCustomDataKey != null && entryCustomDataValue != null) {
ctxEntry.putCustomData(entryCustomDataKey, entryCustomDataValue); ctxEntry.putCustomData(entryCustomDataKey, entryCustomDataValue);
} else {
assert(false);
} }
entryCustomDataKey = null; entryCustomDataKey = null;
@@ -896,8 +887,6 @@ public class ImporterV4 extends Importer {
ctxDeletedObject = null; ctxDeletedObject = null;
return KdbContext.RootDeletedObjects; return KdbContext.RootDeletedObjects;
} else { } else {
assert(false);
String contextName = ""; String contextName = "";
if (ctx != null) { if (ctx != null) {
contextName = ctx.name(); contextName = ctx.name();
@@ -943,35 +932,24 @@ public class ImporterV4 extends Importer {
} }
private void ReadUnknown(XmlPullParser xpp) throws XmlPullParserException, IOException { private void ReadUnknown(XmlPullParser xpp) throws XmlPullParserException, IOException {
assert(false);
if ( xpp.isEmptyElementTag() ) return; if ( xpp.isEmptyElementTag() ) return;
String unknownName = xpp.getName();
ProcessNode(xpp); ProcessNode(xpp);
while (xpp.next() != XmlPullParser.END_DOCUMENT ) { while (xpp.next() != XmlPullParser.END_DOCUMENT ) {
if ( xpp.getEventType() == XmlPullParser.END_TAG ) break; if ( xpp.getEventType() == XmlPullParser.END_TAG ) break;
if ( xpp.getEventType() == XmlPullParser.START_TAG ) continue; if ( xpp.getEventType() == XmlPullParser.START_TAG ) continue;
ReadUnknown(xpp); ReadUnknown(xpp);
} }
assert(xpp.getName().equals(unknownName));
} }
private boolean ReadBool(XmlPullParser xpp, boolean bDefault) throws IOException, XmlPullParserException { private boolean ReadBool(XmlPullParser xpp, boolean bDefault) throws IOException, XmlPullParserException {
String str = ReadString(xpp); String str = ReadString(xpp);
if ( str.equalsIgnoreCase("true") ) { return str.equalsIgnoreCase("true")
return true; || !str.equalsIgnoreCase("false")
} else if ( str.equalsIgnoreCase("false") ) { && bDefault;
return false; }
} else {
return bDefault;
}
}
private UUID ReadUuid(XmlPullParser xpp) throws IOException, XmlPullParserException { private UUID ReadUuid(XmlPullParser xpp) throws IOException, XmlPullParserException {
String encoded = ReadString(xpp); String encoded = ReadString(xpp);
@@ -1050,7 +1028,19 @@ public class ImporterV4 extends Importer {
return new ProtectedString(false, ReadString(xpp)); return new ProtectedString(false, ReadString(xpp));
} }
private ProtectedBinary createProtectedBinaryFromData(boolean protection, byte[] data) throws IOException {
if (data.length > MemUtil.BUFFER_SIZE_BYTES) {
File file = new File(streamDir, getUnusedCacheFileName());
try (FileOutputStream outputStream = new FileOutputStream(file)) {
outputStream.write(data);
}
return new ProtectedBinary(protection, file, data.length);
} else {
return new ProtectedBinary(protection, data);
}
}
private ProtectedBinary ReadProtectedBinary(XmlPullParser xpp) throws XmlPullParserException, IOException { private ProtectedBinary ReadProtectedBinary(XmlPullParser xpp) throws XmlPullParserException, IOException {
String ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef); String ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef);
if (ref != null) { if (ref != null) {
@@ -1068,10 +1058,13 @@ public class ImporterV4 extends Importer {
byte[] buf = ProcessNode(xpp); byte[] buf = ProcessNode(xpp);
if ( buf != null ) return new ProtectedBinary(true, buf); if ( buf != null ) {
createProtectedBinaryFromData(true, buf);
}
String base64 = ReadString(xpp); String base64 = ReadString(xpp);
if ( base64.length() == 0 ) return ProtectedBinary.EMPTY; if ( base64.length() == 0 )
return ProtectedBinary.EMPTY;
byte[] data = Base64Coder.decode(base64); byte[] data = Base64Coder.decode(base64);
@@ -1079,7 +1072,7 @@ public class ImporterV4 extends Importer {
data = MemUtil.decompress(data); data = MemUtil.decompress(data);
} }
return new ProtectedBinary(false, data); return createProtectedBinaryFromData(false, data);
} }
private String ReadString(XmlPullParser xpp) throws IOException, XmlPullParserException { private String ReadString(XmlPullParser xpp) throws IOException, XmlPullParserException {
@@ -1105,13 +1098,14 @@ public class ImporterV4 extends Importer {
} }
private byte[] ProcessNode(XmlPullParser xpp) throws XmlPullParserException, IOException { private byte[] ProcessNode(XmlPullParser xpp) throws XmlPullParserException, IOException {
assert(xpp.getEventType() == XmlPullParser.START_TAG); //(xpp.getEventType() == XmlPullParser.START_TAG);
byte[] buf = null; byte[] buf = null;
if ( xpp.getAttributeCount() > 0 ) { if ( xpp.getAttributeCount() > 0 ) {
String protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected); String protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected);
if ( protect != null && protect.equalsIgnoreCase(PwDatabaseV4XML.ValTrue) ) { if ( protect != null && protect.equalsIgnoreCase(PwDatabaseV4XML.ValTrue) ) {
// TODO stream for encrypted data
String encrypted = ReadStringRaw(xpp); String encrypted = ReadStringRaw(xpp);
if ( encrypted.length() > 0 ) { if ( encrypted.length() > 0 ) {

View File

@@ -23,10 +23,10 @@ import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwDbHeaderV4; import com.kunzisoft.keepass.database.PwDbHeaderV4;
import com.kunzisoft.keepass.database.security.ProtectedBinary; import com.kunzisoft.keepass.database.security.ProtectedBinary;
import com.kunzisoft.keepass.stream.LEDataOutputStream; import com.kunzisoft.keepass.stream.LEDataOutputStream;
import com.kunzisoft.keepass.utils.MemUtil;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Arrays;
public class PwDbInnerHeaderOutputV4 { public class PwDbInnerHeaderOutputV4 {
private PwDatabaseV4 db; private PwDatabaseV4 db;
@@ -50,19 +50,18 @@ public class PwDbInnerHeaderOutputV4 {
los.writeInt(streamKeySize); los.writeInt(streamKeySize);
los.write(header.innerRandomStreamKey); los.write(header.innerRandomStreamKey);
for (ProtectedBinary bin : db.getBinPool().binaries()) { for (ProtectedBinary protectedBinary : db.getBinPool().binaries()) {
byte flag = PwDbHeaderV4.KdbxBinaryFlags.None; byte flag = PwDbHeaderV4.KdbxBinaryFlags.None;
if (bin.isProtected()) { if (protectedBinary.isProtected()) {
flag |= PwDbHeaderV4.KdbxBinaryFlags.Protected; flag |= PwDbHeaderV4.KdbxBinaryFlags.Protected;
} }
byte[] binData = bin.getData();
los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary); los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary);
los.writeInt(bin.length() + 1); los.writeInt((int) protectedBinary.length() + 1); // TODO verify
los.write(flag); los.write(flag);
los.write(binData);
Arrays.fill(binData, (byte)0); MemUtil.readBytes(protectedBinary.getData(),
buffer -> los.write(buffer));
} }
los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader); los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader);

View File

@@ -350,10 +350,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
writeObject(PwDatabaseV4XML.ElemUuid, group.getUUID()); writeObject(PwDatabaseV4XML.ElemUuid, group.getUUID());
writeObject(PwDatabaseV4XML.ElemName, group.getName()); writeObject(PwDatabaseV4XML.ElemName, group.getName());
writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes()); writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes());
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().iconId); writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().getIconId());
if (!group.getCustomIcon().equals(PwIconCustom.ZERO)) { if (!group.getIconCustom().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getCustomIcon().uuid); writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getIconCustom().getUUID());
} }
writeList(PwDatabaseV4XML.ElemTimes, group); writeList(PwDatabaseV4XML.ElemTimes, group);
@@ -375,10 +375,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
xml.startTag(null, PwDatabaseV4XML.ElemEntry); xml.startTag(null, PwDatabaseV4XML.ElemEntry);
writeObject(PwDatabaseV4XML.ElemUuid, entry.getUUID()); writeObject(PwDatabaseV4XML.ElemUuid, entry.getUUID());
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().iconId); writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().getIconId());
if (!entry.getCustomIcon().equals(PwIconCustom.ZERO)) { if (!entry.getIconCustom().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getCustomIcon().uuid); writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getIconCustom().getUUID());
} }
writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor()); writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor());
@@ -394,15 +394,14 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
if (!isHistory) { if (!isHistory) {
writeList(PwDatabaseV4XML.ElemHistory, entry.getHistory(), true); writeList(PwDatabaseV4XML.ElemHistory, entry.getHistory(), true);
} else {
assert(entry.sizeOfHistory() == 0);
} }
// else entry.sizeOfHistory() == 0
xml.endTag(null, PwDatabaseV4XML.ElemEntry); xml.endTag(null, PwDatabaseV4XML.ElemEntry);
} }
private void writeObject(String key, ProtectedBinary value, boolean allowRef) throws IllegalArgumentException, IllegalStateException, IOException { private void writeObject(String key, ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException {
assert(key != null && value != null); assert(key != null && value != null);
xml.startTag(null, PwDatabaseV4XML.ElemBinary); xml.startTag(null, PwDatabaseV4XML.ElemBinary);
@@ -411,11 +410,8 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
xml.endTag(null, PwDatabaseV4XML.ElemKey); xml.endTag(null, PwDatabaseV4XML.ElemKey);
xml.startTag(null, PwDatabaseV4XML.ElemValue); xml.startTag(null, PwDatabaseV4XML.ElemValue);
String strRef = null; int ref = mPM.getBinPool().findKey(value);
if (allowRef) { String strRef = Integer.toString(ref);
int ref = mPM.getBinPool().poolFind(value);
strRef = Integer.toString(ref);
}
if (strRef != null) { if (strRef != null) {
xml.attribute(null, PwDatabaseV4XML.AttrRef, strRef); xml.attribute(null, PwDatabaseV4XML.AttrRef, strRef);
@@ -427,33 +423,85 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
xml.endTag(null, PwDatabaseV4XML.ElemBinary); xml.endTag(null, PwDatabaseV4XML.ElemBinary);
} }
/*
TODO Make with pipe
private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException { private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException {
if (value.isProtected()) { try (InputStream inputStream = value.getData()) {
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue); if (inputStream == null) {
Log.e(TAG, "Can't write a null input stream.");
int valLength = value.length(); return;
if (valLength > 0) { }
byte[] encoded = new byte[valLength];
randomStream.processBytes(value.getData(), 0, valLength, encoded, 0); if (value.isProtected()) {
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue);
xml.text(String.valueOf(Base64Coder.encode(encoded)));
} try (InputStream cypherInputStream =
IOUtil.pipe(inputStream,
} else { o -> new org.spongycastle.crypto.io.CipherOutputStream(o, randomStream))) {
if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) { writeInputStreamInBase64(cypherInputStream);
xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue); }
byte[] raw = value.getData();
byte[] compressed = MemUtil.compress(raw); } else {
xml.text(String.valueOf(Base64Coder.encode(compressed))); if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) {
} else {
byte[] raw = value.getData(); xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue);
xml.text(String.valueOf(Base64Coder.encode(raw)));
} try (InputStream gZipInputStream =
IOUtil.pipe(inputStream, GZIPOutputStream::new, (int) value.length())) {
} writeInputStreamInBase64(gZipInputStream);
}
} else {
writeInputStreamInBase64(inputStream);
}
}
}
} }
private void writeInputStreamInBase64(InputStream inputStream) throws IOException {
try (InputStream base64InputStream =
IOUtil.pipe(inputStream,
o -> new Base64OutputStream(o, DEFAULT))) {
MemUtil.readBytes(base64InputStream,
buffer -> xml.text(Arrays.toString(buffer)));
}
}
//*/
//*
private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException {
int valLength = (int) value.length();
if (valLength > 0) {
byte[] buffer = new byte[valLength];
if (valLength == value.getData().read(buffer, 0, valLength)) {
if (value.isProtected()) {
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue);
byte[] encoded = new byte[valLength];
randomStream.processBytes(buffer, 0, valLength, encoded, 0);
xml.text(String.valueOf(Base64Coder.encode(encoded)));
} else {
if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) {
xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue);
byte[] compressData = MemUtil.compress(buffer);
xml.text(String.valueOf(Base64Coder.encode(compressData)));
} else {
xml.text(String.valueOf(Base64Coder.encode(buffer)));
}
}
} else {
Log.e(TAG, "Unable to read the stream of the protected binary");
}
}
}
//*/
private void writeObject(String name, String value, boolean filterXmlChars) throws IllegalArgumentException, IllegalStateException, IOException { private void writeObject(String name, String value, boolean filterXmlChars) throws IllegalArgumentException, IllegalStateException, IOException {
assert(name != null && value != null); assert(name != null && value != null);
@@ -616,7 +664,7 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
assert(binaries != null); assert(binaries != null);
for (Entry<String, ProtectedBinary> pair : binaries.entrySet()) { for (Entry<String, ProtectedBinary> pair : binaries.entrySet()) {
writeObject(pair.getKey(), pair.getValue(), true); writeObject(pair.getKey(), pair.getValue());
} }
} }
@@ -701,8 +749,8 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
for (PwIconCustom icon : customIcons) { for (PwIconCustom icon : customIcons) {
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem); xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem);
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid); writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.getUUID());
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.imageData))); writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.getImageData())));
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem); xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem);
} }

View File

@@ -79,12 +79,12 @@ public class PwEntryOutputV3 {
// Group ID // Group ID
mOS.write(GROUPID_FIELD_TYPE); mOS.write(GROUPID_FIELD_TYPE);
mOS.write(LONG_FOUR); mOS.write(LONG_FOUR);
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getGroupId())); mOS.write(LEDataOutputStream.writeIntBuf(mPE.getParent().getGroupId()));
// Image ID // Image ID
mOS.write(IMAGEID_FIELD_TYPE); mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(LONG_FOUR); mOS.write(LONG_FOUR);
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().iconId)); mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().getIconId()));
// Title // Title
//byte[] title = mPE.title.getBytes("UTF-8"); //byte[] title = mPE.title.getBytes("UTF-8");

View File

@@ -93,7 +93,7 @@ public class PwGroupOutputV3 {
// Image ID // Image ID
mOS.write(IMAGEID_FIELD_TYPE); mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(IMAGEID_FIELD_SIZE); mOS.write(IMAGEID_FIELD_SIZE);
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().iconId)); mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().getIconId()));
// Level // Level
mOS.write(LEVEL_FIELD_TYPE); mOS.write(LEVEL_FIELD_TYPE);

View File

@@ -17,8 +17,10 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator; import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
import java.util.Date; import java.util.Date;

View File

@@ -17,7 +17,10 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntry;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;

View File

@@ -17,8 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.utils.StrUtil; import com.kunzisoft.keepass.utils.StrUtil;
import com.kunzisoft.keepass.utils.UuidUtil; import com.kunzisoft.keepass.utils.UuidUtil;

View File

@@ -17,8 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.utils.StrUtil; import com.kunzisoft.keepass.utils.StrUtil;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.search; package com.kunzisoft.keepass.database.search;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@@ -42,7 +42,7 @@ import java.util.Locale;
import java.util.Queue; import java.util.Queue;
public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch, PwEntrySearch>, public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch, PwEntrySearch>,
PwGroupSearch extends PwGroup<PwGroupSearch, PwGroupSearch, PwEntrySearch>, PwGroupSearch extends PwGroup<PwGroupSearch, PwEntrySearch>,
PwEntrySearch extends PwEntry<PwGroupSearch>> { PwEntrySearch extends PwEntry<PwGroupSearch>> {
private final Context mCtx; private final Context mCtx;
@@ -54,31 +54,33 @@ public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch,
private boolean omitBackup() { private boolean omitBackup() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mCtx); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mCtx);
return prefs.getBoolean(mCtx.getString(R.string.omitbackup_key), mCtx.getResources().getBoolean(R.bool.omitbackup_default)); return prefs.getBoolean(mCtx.getString(R.string.omitbackup_key), mCtx.getResources().getBoolean(R.bool.omitbackup_default));
} }
public PwGroupSearch search(PwDatabaseVersion pm, String qStr) { public PwGroupSearch search(PwDatabaseVersion pm, String qStr, int max) {
PwGroupSearch group = pm.createGroup(); PwGroupSearch group = pm.createGroup();
group.setName(mCtx.getString(R.string.search_results)); group.setName("\"" + qStr + "\"");
group.setEntries(new ArrayList<>()); group.setEntries(new ArrayList<>());
// Search all entries // Search all entries
Locale loc = Locale.getDefault(); Locale loc = Locale.getDefault();
qStr = qStr.toLowerCase(loc); qStr = qStr.toLowerCase(loc);
boolean isOmitBackup = omitBackup(); boolean isOmitBackup = omitBackup();
// TODO Search from the current group
Queue<PwGroupSearch> worklist = new LinkedList<>(); Queue<PwGroupSearch> worklist = new LinkedList<>();
if (pm.getRootGroup() != null) { if (pm.getRootGroup() != null) {
worklist.add(pm.getRootGroup()); worklist.add(pm.getRootGroup());
} }
while (worklist.size() != 0) { while (worklist.size() != 0) {
PwGroupSearch top = worklist.remove(); PwGroupSearch top = worklist.remove();
if (pm.isGroupSearchable(top, isOmitBackup)) { if (pm.isGroupSearchable(top, isOmitBackup)) {
for (PwEntrySearch entry : top.getChildEntries()) { for (PwEntrySearch entry : top.getChildEntries()) {
processEntries(entry, group.getChildEntries(), qStr, loc); processEntries(entry, group.getChildEntries(), qStr, loc);
if (group.numbersOfChildEntries() >= max)
return group;
} }
for (PwGroupSearch childGroup : top.getChildGroups()) { for (PwGroupSearch childGroup : top.getChildGroups()) {

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database.search;
/** /**
* @author bpellin * @author bpellin

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database; package com.kunzisoft.keepass.database.search;
public class SearchParametersV4 extends SearchParameters implements Cloneable { public class SearchParametersV4 extends SearchParameters implements Cloneable {
public static SearchParametersV4 DEFAULT = new SearchParametersV4(); public static SearchParametersV4 DEFAULT = new SearchParametersV4();

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2018 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePass DX. * This file is part of KeePass DX.
* *
@@ -19,48 +19,131 @@
*/ */
package com.kunzisoft.keepass.database.security; package com.kunzisoft.keepass.database.security;
import java.io.Serializable; import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
public class ProtectedBinary implements Serializable { public class ProtectedBinary implements Parcelable {
private static final String TAG = ProtectedBinary.class.getName();
public final static ProtectedBinary EMPTY = new ProtectedBinary(); public final static ProtectedBinary EMPTY = new ProtectedBinary();
private byte[] data;
private boolean protect; private boolean protect;
private byte[] data;
private File dataFile;
private int size;
public boolean isProtected() { public boolean isProtected() {
return protect; return protect;
} }
public int length() { public long length() {
if (data == null) { if (data != null)
return 0; return data.length;
} if (dataFile != null)
return size;
return data.length; return 0;
} }
public ProtectedBinary() { private ProtectedBinary() {
this(false, new byte[0]); this.protect = false;
this.data = null;
this.dataFile = null;
this.size = 0;
} }
public ProtectedBinary(boolean enableProtection, byte[] data) { public ProtectedBinary(boolean enableProtection, byte[] data) {
protect = enableProtection; this.protect = enableProtection;
this.data = data; this.data = data;
this.dataFile = null;
} if (data != null)
this.size = data.length;
else
// TODO: replace the byte[] with something like ByteBuffer to make the return this.size = 0;
// value immutable, so we don't have to worry about making deep copies
public byte[] getData() {
return data;
}
public boolean equals(ProtectedBinary rhs) {
return (protect == rhs.protect) && Arrays.equals(data, rhs.data);
} }
public ProtectedBinary(boolean enableProtection, File dataFile, int size) {
this.protect = enableProtection;
this.data = null;
this.dataFile = dataFile;
this.size = size;
}
private ProtectedBinary(Parcel in) {
protect = in.readByte() != 0;
in.readByteArray(data);
dataFile = new File(in.readString());
size = in.readInt();
}
public InputStream getData() throws IOException {
if (data != null)
return new ByteArrayInputStream(data);
else if (dataFile != null)
return new FileInputStream(dataFile);
else
return null;
}
public void clear() {
data = null;
if (dataFile != null && !dataFile.delete())
Log.e(TAG, "Unable to delete temp file " + dataFile.getAbsolutePath());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProtectedBinary that = (ProtectedBinary) o;
return protect == that.protect &&
size == that.size &&
Arrays.equals(data, that.data) &&
dataFile != null &&
dataFile.equals(that.dataFile);
}
@Override
public int hashCode() {
int result = 0;
result = 31 * result + (protect ? 1 : 0);
result = 31 * result + dataFile.hashCode();
result = 31 * result + Integer.valueOf(size).hashCode();
result = 31 * result + Arrays.hashCode(data);
return result;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (protect ? 1 : 0));
dest.writeByteArray(data);
dest.writeString(dataFile.getAbsolutePath());
dest.writeInt(size);
}
public static final Creator<ProtectedBinary> CREATOR = new Creator<ProtectedBinary>() {
@Override
public ProtectedBinary createFromParcel(Parcel in) {
return new ProtectedBinary(in);
}
@Override
public ProtectedBinary[] newArray(int size) {
return new ProtectedBinary[size];
}
};
} }

View File

@@ -19,38 +19,66 @@
*/ */
package com.kunzisoft.keepass.database.security; package com.kunzisoft.keepass.database.security;
import java.io.Serializable; import android.os.Parcel;
import android.os.Parcelable;
public class ProtectedString implements Parcelable {
public class ProtectedString implements Serializable {
private String string;
private boolean protect; private boolean protect;
private String string;
public boolean isProtected() {
return protect;
}
public int length() {
if (string == null) {
return 0;
}
return string.length();
}
public ProtectedString() { public ProtectedString() {
this(false, ""); this(false, "");
} }
public ProtectedString(ProtectedString toCopy) { public ProtectedString(ProtectedString toCopy) {
this.string = toCopy.string;
this.protect = toCopy.protect; this.protect = toCopy.protect;
this.string = toCopy.string;
} }
public ProtectedString(boolean enableProtection, String string) { public ProtectedString(boolean enableProtection, String string) {
protect = enableProtection; this.protect = enableProtection;
this.string = string; this.string = string;
}
public ProtectedString(Parcel in) {
protect = in.readByte() != 0;
string = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (protect ? 1 : 0));
dest.writeString(string);
}
public static final Parcelable.Creator<ProtectedString> CREATOR = new Parcelable.Creator<ProtectedString>() {
@Override
public ProtectedString createFromParcel(Parcel in) {
return new ProtectedString(in);
}
@Override
public ProtectedString[] newArray(int size) {
return new ProtectedString[size];
}
};
public boolean isProtected() {
return protect;
}
public int length() {
if (string == null) {
return 0;
}
return string.length();
} }
@Override @Override

View File

@@ -35,9 +35,9 @@ import android.widget.Toast;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwIcon; import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.icons.IconPackChooser;
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION; import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION;
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE; import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE;
@@ -49,9 +49,11 @@ public class GroupEditDialogFragment extends DialogFragment
public static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP"; public static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
public static final String KEY_NAME = "KEY_NAME"; public static final String KEY_NAME = "KEY_NAME";
public static final String KEY_ICON_ID = "KEY_ICON_ID"; public static final String KEY_ICON = "KEY_ICON";
public static final String KEY_ACTION_ID = "KEY_ACTION_ID"; public static final String KEY_ACTION_ID = "KEY_ACTION_ID";
private Database database;
private EditGroupListener editGroupListener; private EditGroupListener editGroupListener;
private EditGroupDialogAction editGroupDialogAction; private EditGroupDialogAction editGroupDialogAction;
@@ -59,6 +61,7 @@ public class GroupEditDialogFragment extends DialogFragment
private PwIcon iconGroup; private PwIcon iconGroup;
private ImageView iconButton; private ImageView iconButton;
private int iconColor;
public enum EditGroupDialogAction { public enum EditGroupDialogAction {
CREATION, UPDATE, NONE; CREATION, UPDATE, NONE;
@@ -76,10 +79,10 @@ public class GroupEditDialogFragment extends DialogFragment
return fragment; return fragment;
} }
public static GroupEditDialogFragment build(PwNode group) { public static GroupEditDialogFragment build(PwGroup group) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString(KEY_NAME, group.getDisplayTitle()); bundle.putString(KEY_NAME, group.getName());
bundle.putSerializable(KEY_ICON_ID, group.getIcon()); bundle.putParcelable(KEY_ICON, group.getIcon());
bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal()); bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal());
GroupEditDialogFragment fragment = new GroupEditDialogFragment(); GroupEditDialogFragment fragment = new GroupEditDialogFragment();
fragment.setArguments(bundle); fragment.setArguments(bundle);
@@ -112,20 +115,21 @@ public class GroupEditDialogFragment extends DialogFragment
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
int[] attrs = {android.R.attr.textColorPrimary}; int[] attrs = {android.R.attr.textColorPrimary};
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(attrs); TypedArray ta = getActivity().getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE); iconColor = ta.getColor(0, Color.WHITE);
// Init elements // Init elements
database = App.getDB();
editGroupDialogAction = EditGroupDialogAction.NONE; editGroupDialogAction = EditGroupDialogAction.NONE;
nameGroup = ""; nameGroup = "";
iconGroup = App.getDB().getPwDatabase().getIconFactory().getFolderIcon(); iconGroup = database.getPwDatabase().getIconFactory().getFolderIcon();
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ACTION_ID) && savedInstanceState.containsKey(KEY_ACTION_ID)
&& savedInstanceState.containsKey(KEY_NAME) && savedInstanceState.containsKey(KEY_NAME)
&& savedInstanceState.containsKey(KEY_ICON_ID)) { && savedInstanceState.containsKey(KEY_ICON)) {
editGroupDialogAction = getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID)); editGroupDialogAction = getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID));
nameGroup = savedInstanceState.getString(KEY_NAME); nameGroup = savedInstanceState.getString(KEY_NAME);
iconGroup = (PwIcon) savedInstanceState.getSerializable(KEY_ICON_ID); iconGroup = savedInstanceState.getParcelable(KEY_ICON);
} else { } else {
@@ -135,30 +139,16 @@ public class GroupEditDialogFragment extends DialogFragment
if (getArguments() != null if (getArguments() != null
&& getArguments().containsKey(KEY_NAME) && getArguments().containsKey(KEY_NAME)
&& getArguments().containsKey(KEY_ICON_ID)) { && getArguments().containsKey(KEY_ICON)) {
nameGroup = getArguments().getString(KEY_NAME); nameGroup = getArguments().getString(KEY_NAME);
iconGroup = (PwIcon) getArguments().getSerializable(KEY_ICON_ID); iconGroup = getArguments().getParcelable(KEY_ICON);
} }
} }
// populate the name // populate the name
nameField.setText(nameGroup); nameField.setText(nameGroup);
// populate the icon // populate the icon
if (IconPackChooser.getSelectedIconPack(getContext()).tintable()) { assignIconView();
App.getDB().getDrawFactory()
.assignDatabaseIconTo(
getContext(),
iconButton,
iconGroup,
true,
iconColor);
} else {
App.getDB().getDrawFactory()
.assignDatabaseIconTo(
getContext(),
iconButton,
iconGroup);
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(root) builder.setView(root)
@@ -195,18 +185,26 @@ public class GroupEditDialogFragment extends DialogFragment
return builder.create(); return builder.create();
} }
private void assignIconView() {
database.getDrawFactory()
.assignDatabaseIconTo(
getContext(),
iconButton,
iconGroup,
iconColor);
}
@Override @Override
public void iconPicked(Bundle bundle) { public void iconPicked(Bundle bundle) {
int selectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID); iconGroup = bundle.getParcelable(IconPickerDialogFragment.KEY_ICON_STANDARD);
iconButton.setImageResource(IconPackChooser.getSelectedIconPack(getContext()).iconToResId(selectedIconID)); assignIconView();
iconGroup = App.getDB().getPwDatabase().getIconFactory().getIcon(selectedIconID);
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
outState.putInt(KEY_ACTION_ID, editGroupDialogAction.ordinal()); outState.putInt(KEY_ACTION_ID, editGroupDialogAction.ordinal());
outState.putString(KEY_NAME, nameGroup); outState.putString(KEY_NAME, nameGroup);
outState.putSerializable(KEY_ICON_ID, iconGroup); outState.putParcelable(KEY_ICON, iconGroup);
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }

View File

@@ -37,14 +37,16 @@ import android.widget.GridView;
import android.widget.ImageView; import android.widget.ImageView;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.icons.IconPack; import com.kunzisoft.keepass.icons.IconPack;
import com.kunzisoft.keepass.icons.IconPackChooser; import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.stylish.StylishActivity; import com.kunzisoft.keepass.stylish.StylishActivity;
public class IconPickerDialogFragment extends DialogFragment { public class IconPickerDialogFragment extends DialogFragment {
public static final String KEY_ICON_ID = "icon_id";
public static final int UNDEFINED_ICON_ID = -1; public static final String KEY_ICON_STANDARD = "KEY_ICON_STANDARD";
private IconPickerListener iconPickerListener; private IconPickerListener iconPickerListener;
private IconPack iconPack; private IconPack iconPack;
@@ -85,7 +87,7 @@ public class IconPickerDialogFragment extends DialogFragment {
currIconGridView.setOnItemClickListener((parent, v, position, id) -> { currIconGridView.setOnItemClickListener((parent, v, position, id) -> {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putInt(KEY_ICON_ID, position); bundle.putParcelable(KEY_ICON_STANDARD, new PwIconStandard(position));
iconPickerListener.iconPicked(bundle); iconPickerListener.iconPicked(bundle);
dismiss(); dismiss();
}); });

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.keyboard; package com.kunzisoft.keepass.dialogs;
import android.app.Dialog; import android.app.Dialog;
import android.content.Intent; import android.content.Intent;
@@ -28,16 +28,14 @@ import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager;
import com.kunzisoft.keepass.BuildConfig; import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.utils.Util; import com.kunzisoft.keepass.utils.Util;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
public class KeyboardExplanationDialog extends DialogFragment { public class KeyboardExplanationDialogFragment extends DialogFragment {
@NonNull @NonNull
@Override @Override

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.fileselect; package com.kunzisoft.keepass.fileselect;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@@ -38,6 +37,9 @@ public class FileInformationDialogFragment extends DialogFragment {
private static final String FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG"; private static final String FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG";
private View fileSizeContainerView;
private View fileModificationContainerView;
public static FileInformationDialogFragment newInstance(FileSelectBean fileSelectBean) { public static FileInformationDialogFragment newInstance(FileSelectBean fileSelectBean) {
FileInformationDialogFragment fileInformationDialogFragment = FileInformationDialogFragment fileInformationDialogFragment =
new FileInformationDialogFragment(); new FileInformationDialogFragment();
@@ -53,38 +55,44 @@ public class FileInformationDialogFragment extends DialogFragment {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater(); LayoutInflater inflater = getActivity().getLayoutInflater();
View root = inflater.inflate(R.layout.file_selection_information, null); View root = inflater.inflate(R.layout.file_selection_information, null);
TextView fileNameView = root.findViewById(R.id.file_filename);
TextView filePathView = root.findViewById(R.id.file_path);
fileSizeContainerView = root.findViewById(R.id.file_size_container);
TextView fileSizeView = root.findViewById(R.id.file_size);
fileModificationContainerView = root.findViewById(R.id.file_modification_container);
TextView fileModificationView = root.findViewById(R.id.file_modification);
if (getArguments() != null && getArguments().containsKey(FILE_SELECT_BEEN_ARG)) { if (getArguments() != null && getArguments().containsKey(FILE_SELECT_BEEN_ARG)) {
FileSelectBean fileSelectBean = (FileSelectBean) getArguments().getSerializable(FILE_SELECT_BEEN_ARG); FileSelectBean fileSelectBean = (FileSelectBean) getArguments().getSerializable(FILE_SELECT_BEEN_ARG);
TextView fileWarningView = (TextView) root.findViewById(R.id.file_warning);
if(fileSelectBean != null) { if(fileSelectBean != null) {
TextView fileNameView = (TextView) root.findViewById(R.id.file_filename);
TextView filePathView = (TextView) root.findViewById(R.id.file_path);
TextView fileSizeView = (TextView) root.findViewById(R.id.file_size);
TextView fileModificationView = (TextView) root.findViewById(R.id.file_modification);
fileWarningView.setVisibility(View.GONE);
fileNameView.setText(fileSelectBean.getFileName());
filePathView.setText(Uri.decode(fileSelectBean.getFileUri().toString())); filePathView.setText(Uri.decode(fileSelectBean.getFileUri().toString()));
fileSizeView.setText(String.valueOf(fileSelectBean.getSize())); fileNameView.setText(fileSelectBean.getFileName());
fileModificationView.setText(DateFormat.getDateTimeInstance()
.format(fileSelectBean.getLastModification())); if(fileSelectBean.notFound()) {
if(fileSelectBean.notFound()) hideFileInfo();
showFileNotFound(fileWarningView); } else {
showFileInfo();
fileSizeView.setText(String.valueOf(fileSelectBean.getSize()));
fileModificationView.setText(DateFormat.getDateTimeInstance()
.format(fileSelectBean.getLastModification()));
}
} else } else
showFileNotFound(fileWarningView); hideFileInfo();
} }
builder.setView(root); builder.setView(root);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { builder.setPositiveButton(android.R.string.ok, (dialog, id) -> {});
public void onClick(DialogInterface dialog, int id) {
}
});
return builder.create(); return builder.create();
} }
private void showFileNotFound(TextView fileWarningView) { private void showFileInfo() {
fileWarningView.setVisibility(View.VISIBLE); fileSizeContainerView.setVisibility(View.VISIBLE);
fileWarningView.setText(R.string.file_not_found); fileModificationContainerView.setVisibility(View.VISIBLE);
}
private void hideFileInfo() {
fileSizeContainerView.setVisibility(View.GONE);
fileModificationContainerView.setVisibility(View.GONE);
} }
} }

View File

@@ -94,7 +94,7 @@ public class FileSelectActivity extends StylishActivity implements
private static final String EXTRA_STAY = "EXTRA_STAY"; private static final String EXTRA_STAY = "EXTRA_STAY";
private FileSelectAdapter mAdapter; private FileSelectAdapter mAdapter;
private View fileListTitle; private View fileListContainer;
private View createButtonView; private View createButtonView;
private View browseButtonView; private View browseButtonView;
private View openButtonView; private View openButtonView;
@@ -148,7 +148,7 @@ public class FileSelectActivity extends StylishActivity implements
fileHistory = App.getFileHistory(); fileHistory = App.getFileHistory();
setContentView(R.layout.file_selection); setContentView(R.layout.file_selection);
fileListTitle = findViewById(R.id.file_list_title); fileListContainer = findViewById(R.id.container_file_list);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(""); toolbar.setTitle("");
@@ -284,7 +284,7 @@ public class FileSelectActivity extends StylishActivity implements
int warning = -1; int warning = -1;
String state = Environment.getExternalStorageState(); String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
warning = R.string.warning_read_only; warning = R.string.read_only_warning;
} else if (!state.equals(Environment.MEDIA_MOUNTED)) { } else if (!state.equals(Environment.MEDIA_MOUNTED)) {
warning = R.string.warning_unmounted; warning = R.string.warning_unmounted;
} }
@@ -303,7 +303,7 @@ public class FileSelectActivity extends StylishActivity implements
super.onResume(); super.onResume();
updateExternalStorageWarning(); updateExternalStorageWarning();
updateTitleFileListView(); updateFileListVisibility();
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
} }
@@ -434,11 +434,11 @@ public class FileSelectActivity extends StylishActivity implements
createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment"); createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment");
} }
private void updateTitleFileListView() { private void updateFileListVisibility() {
if(mAdapter.getItemCount() == 0) if(mAdapter.getItemCount() == 0)
fileListTitle.setVisibility(View.INVISIBLE); fileListContainer.setVisibility(View.INVISIBLE);
else else
fileListTitle.setVisibility(View.VISIBLE); fileListContainer.setVisibility(View.VISIBLE);
} }
/** /**
@@ -593,7 +593,7 @@ public class FileSelectActivity extends StylishActivity implements
// Add to recent files // Add to recent files
fileHistory.createFile(mUri, getFilename()); fileHistory.createFile(mUri, getFilename());
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
updateTitleFileListView(); updateFileListVisibility();
GroupActivity.launch(FileSelectActivity.this); GroupActivity.launch(FileSelectActivity.this);
}); });
} }
@@ -629,7 +629,7 @@ public class FileSelectActivity extends StylishActivity implements
R.string.file_not_found, Toast.LENGTH_LONG) R.string.file_not_found, Toast.LENGTH_LONG)
.show(); .show();
} }
updateTitleFileListView(); updateFileListVisibility();
}, fileHistory).execute(itemPosition); }, fileHistory).execute(itemPosition);
} }
@@ -647,7 +647,7 @@ public class FileSelectActivity extends StylishActivity implements
new DeleteFileHistoryAsyncTask(() -> { new DeleteFileHistoryAsyncTask(() -> {
fileHistory.deleteFile(fileSelectBean.getFileUri()); fileHistory.deleteFile(fileSelectBean.getFileUri());
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
updateTitleFileListView(); updateFileListVisibility();
}, fileHistory, mAdapter).execute(fileSelectBean); }, fileHistory, mAdapter).execute(fileSelectBean);
return true; return true;
} }

View File

@@ -88,16 +88,6 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
else else
holder.fileName.setText(fileSelectBean.getFileName()); holder.fileName.setText(fileSelectBean.getFileName());
holder.fileName.setTextSize(PreferencesUtil.getListTextSize(context)); holder.fileName.setTextSize(PreferencesUtil.getListTextSize(context));
// Set warning
if (fileSelectBean.notFound()) {
holder.fileInformation.setColorFilter(
warningColor,
android.graphics.PorterDuff.Mode.MULTIPLY);
} else {
holder.fileInformation.setColorFilter(
defaultColor,
android.graphics.PorterDuff.Mode.MULTIPLY);
}
// Click on information // Click on information
if (fileInformationShowListener != null) if (fileInformationShowListener != null)
holder.fileInformation.setOnClickListener(new FileInformationClickListener(fileSelectBean)); holder.fileInformation.setOnClickListener(new FileInformationClickListener(fileSelectBean));

View File

@@ -65,52 +65,50 @@ public class IconDrawableFactory {
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK); private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
/** /**
* Assign a default database icon to an ImageView * Assign a default database icon to an ImageView and tint it if needed
* *
* @param context Context to build the drawable * @param context Context to build the drawable
* @param iv ImageView that will host the drawable * @param iconView ImageView that will host the drawable
* @param tintColor Use this color to tint tintable icon
*/ */
public void assignDefaultDatabaseIconTo(Context context, ImageView iv) { public void assignDefaultDatabaseIconTo(Context context, ImageView iconView, int tintColor) {
assignDefaultDatabaseIconTo(context, iv, false, Color.WHITE); if (IconPackChooser.getSelectedIconPack(context).tintable()) {
assignDrawableTo(context,
iconView,
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
true,
tintColor);
} else {
assignDrawableTo(context,
iconView,
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
false,
Color.WHITE);
}
} }
/** /**
* Assign a default database icon to an ImageView and tint it * Assign a database icon to an ImageView and tint it if needed
* *
* @param context Context to build the drawable * @param context Context to build the drawable
* @param iv ImageView that will host the drawable * @param iconView ImageView that will host the drawable
*/
public void assignDefaultDatabaseIconTo(Context context, ImageView iv, boolean tint, int tintColor) {
assignDrawableTo(context, iv, IconPackChooser.getSelectedIconPack(context).getDefaultIconId(), tint, tintColor);
}
/**
* Assign a database icon to an ImageView
*
* @param context Context to build the drawable
* @param iv ImageView that will host the drawable
* @param icon The icon from the database * @param icon The icon from the database
* @param tintColor Use this color to tint tintable icon
*/ */
public void assignDatabaseIconTo(Context context, ImageView iv, PwIcon icon) { public void assignDatabaseIconTo(Context context, ImageView iconView, PwIcon icon, int tintColor) {
assignDatabaseIconTo(context, iv, icon, false, Color.WHITE); if (IconPackChooser.getSelectedIconPack(context).tintable()) {
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
iconView,
true,
tintColor);
} else {
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
iconView,
false,
Color.WHITE);
}
} }
/**
* Assign a database icon to an ImageView and tint it
*
* @param context Context to build the drawable
* @param imageView ImageView that will host the drawable
* @param icon The icon from the database
* @param tint true will tint the drawable with tintColor
* @param tintColor Use this color if tint is true
*/
public void assignDatabaseIconTo(Context context, ImageView imageView, PwIcon icon, boolean tint, int tintColor) {
assignDrawableToImageView(getIconDrawable(context, icon, tint, tintColor),
imageView,
tint,
tintColor);
}
/** /**
* Assign an image by its resourceId to an ImageView and tint it * Assign an image by its resourceId to an ImageView and tint it
* *
@@ -221,7 +219,7 @@ public class IconDrawableFactory {
* @return The drawable * @return The drawable
*/ */
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) { private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.iconId); int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
return getIconDrawable(context, resId, isTint, tintColor); return getIconDrawable(context, resId, isTint, tintColor);
} }
@@ -289,14 +287,14 @@ public class IconDrawableFactory {
return blank; return blank;
} }
Drawable draw = (Drawable) customIconMap.get(icon.uuid); Drawable draw = (Drawable) customIconMap.get(icon.getUUID());
if (draw == null) { if (draw == null) {
if (icon.imageData == null) { if (icon.getImageData() == null) {
return blank; return blank;
} }
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.length); Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
// Could not understand custom icon // Could not understand custom icon
if (bitmap == null) { if (bitmap == null) {
@@ -306,7 +304,7 @@ public class IconDrawableFactory {
bitmap = resize(bitmap); bitmap = resize(bitmap);
draw = new BitmapDrawable(context.getResources(), bitmap); draw = new BitmapDrawable(context.getResources(), bitmap);
customIconMap.put(icon.uuid, draw); customIconMap.put(icon.getUUID(), draw);
} }
return draw; return draw;

View File

@@ -29,6 +29,7 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.settings.PreferencesUtil; import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.stylish.StylishActivity; import com.kunzisoft.keepass.stylish.StylishActivity;
@@ -45,6 +46,7 @@ public abstract class LockingActivity extends StylishActivity {
private LockReceiver lockReceiver; private LockReceiver lockReceiver;
private boolean exitLock; private boolean exitLock;
protected boolean readOnly;
/** /**
* Called to start a record time, * Called to start a record time,
@@ -72,6 +74,9 @@ public abstract class LockingActivity extends StylishActivity {
lockReceiver = null; lockReceiver = null;
exitLock = false; exitLock = false;
readOnly = false;
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
} }
public static void checkShutdown(Activity activity) { public static void checkShutdown(Activity activity) {
@@ -116,6 +121,12 @@ public abstract class LockingActivity extends StylishActivity {
TimeoutHelper.recordTime(this); TimeoutHelper.recordTime(this);
} }
@Override
protected void onSaveInstanceState(Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();

View File

@@ -1,4 +1,4 @@
package com.kunzisoft.magikeyboard; package com.kunzisoft.keepass.magikeyboard;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
@@ -9,7 +9,8 @@ import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.util.Log; import android.util.Log;
import com.kunzisoft.keepass_model.Entry; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.model.Entry;
public class EntryRetrieverActivity extends AppCompatActivity { public class EntryRetrieverActivity extends AppCompatActivity {
@@ -43,8 +44,8 @@ public class EntryRetrieverActivity extends AppCompatActivity {
// Show the notification if allowed in Preferences // Show the notification if allowed in Preferences
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(getString(R.string.notification_entry_key), if (sharedPreferences.getBoolean(getString(R.string.keyboard_notification_entry_key),
getResources().getBoolean(R.bool.notification_entry_default))) { getResources().getBoolean(R.bool.keyboard_notification_entry_default))) {
Intent notificationIntent = new Intent(this, KeyboardEntryNotificationService.class); Intent notificationIntent = new Intent(this, KeyboardEntryNotificationService.class);
startService(notificationIntent); startService(notificationIntent);
} }

View File

@@ -1,4 +1,4 @@
package com.kunzisoft.magikeyboard; package com.kunzisoft.keepass.magikeyboard;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -7,19 +7,24 @@ import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v7.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver; import com.kunzisoft.keepass.R;
import com.kunzisoft.magikeyboard.receiver.NotificationDeleteBroadcastReceiver; import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver;
import com.kunzisoft.keepass.magikeyboard.receiver.NotificationDeleteBroadcastReceiver;
import com.kunzisoft.keepass.timeout.TimeoutHelper;
import static android.content.ContentValues.TAG; import static com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
import static com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
public class KeyboardEntryNotificationService extends Service { public class KeyboardEntryNotificationService extends Service {
private static final String TAG = KeyboardEntryNotificationService.class.getName();
private static final String CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.entry.channel"; private static final String CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.entry.channel";
private static final String CHANNEL_NAME_KEYBOARD = "Magikeyboard notification"; private static final String CHANNEL_NAME_KEYBOARD = "Magikeyboard notification";
@@ -80,7 +85,7 @@ public class KeyboardEntryNotificationService extends Service {
PendingIntent.getBroadcast(getApplicationContext(), 0, deleteIntent, 0); PendingIntent.getBroadcast(getApplicationContext(), 0, deleteIntent, 0);
if (MagikIME.getEntryKey() != null) { if (MagikIME.getEntryKey() != null) {
String entryTitle = getString(R.string.notification_entry_content_title_text); String entryTitle = getString(R.string.keyboard_notification_entry_content_title_text);
String entryUsername = ""; String entryUsername = "";
if (!MagikIME.getEntryKey().getTitle().isEmpty()) if (!MagikIME.getEntryKey().getTitle().isEmpty())
entryTitle = MagikIME.getEntryKey().getTitle(); entryTitle = MagikIME.getEntryKey().getTitle();
@@ -89,40 +94,55 @@ public class KeyboardEntryNotificationService extends Service {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD) NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD)
.setSmallIcon(R.drawable.ic_vpn_key_white_24dp) .setSmallIcon(R.drawable.ic_vpn_key_white_24dp)
.setContentTitle(getString(R.string.notification_entry_content_title, entryTitle)) .setContentTitle(getString(R.string.keyboard_notification_entry_content_title, entryTitle))
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_SECRET) .setVisibility(NotificationCompat.VISIBILITY_SECRET)
.setContentText(getString(R.string.notification_entry_content_text, entryUsername)) .setContentText(getString(R.string.keyboard_notification_entry_content_text, entryUsername))
.setAutoCancel(false) .setAutoCancel(false)
.setContentIntent(null) .setContentIntent(null)
.setDeleteIntent(pendingDeleteIntent); .setDeleteIntent(pendingDeleteIntent);
notificationManager.cancel(notificationId); notificationManager.cancel(notificationId);
notificationManager.notify(notificationId, builder.build()); notificationManager.notify(notificationId, builder.build());
}
// TODO Get timeout
/*
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
notificationTimeoutMilliSecs = prefs.getInt(getString(R.string.entry_timeout_key), 100000);
*/
/*
stopTask(cleanNotificationTimer); // Timeout only if notification clear is available
cleanNotificationTimer = new Thread(new Runnable() { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@Override if (sharedPreferences.getBoolean(getString(R.string.keyboard_notification_entry_clear_close_key),
public void run() { getResources().getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
String keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key),
getString(R.string.timeout_default));
try { try {
Thread.sleep(notificationTimeoutMilliSecs); notificationTimeoutMilliSecs = Long.parseLong(keyboardTimeout);
} catch (InterruptedException e) { } catch (NumberFormatException e) {
cleanNotificationTimer = null; notificationTimeoutMilliSecs = TimeoutHelper.DEFAULT_TIMEOUT;
return; }
if (notificationTimeoutMilliSecs != TimeoutHelper.TIMEOUT_NEVER) {
stopTask(cleanNotificationTimer);
cleanNotificationTimer = new Thread(() -> {
int maxPos = 100;
long posDurationMills = notificationTimeoutMilliSecs / maxPos;
for (int pos = maxPos; pos > 0; --pos) {
builder.setProgress(maxPos, pos, false);
notificationManager.notify(notificationId, builder.build());
try {
Thread.sleep(posDurationMills);
} catch (InterruptedException e) {
break;
}
}
try {
pendingDeleteIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, e.getLocalizedMessage());
}
});
cleanNotificationTimer.start();
} }
notificationManager.cancel(notificationId);
} }
}); }
cleanNotificationTimer.start();
*/
} }
private void stopTask(Thread task) { private void stopTask(Thread task) {
@@ -130,12 +150,19 @@ public class KeyboardEntryNotificationService extends Service {
task.interrupt(); task.interrupt();
} }
@Override private void destroyKeyboardNotification() {
public void onDestroy() { stopTask(cleanNotificationTimer);
cleanNotificationTimer = null;
unregisterReceiver(lockBroadcastReceiver); unregisterReceiver(lockBroadcastReceiver);
pendingDeleteIntent.cancel(); pendingDeleteIntent.cancel();
notificationManager.cancel(notificationId); notificationManager.cancel(notificationId);
}
@Override
public void onDestroy() {
destroyKeyboardNotification();
super.onDestroy(); super.onDestroy();
} }

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.magikeyboard; package com.kunzisoft.keepass.magikeyboard;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -28,24 +28,35 @@ import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView; import android.inputmethodservice.KeyboardView;
import android.media.AudioManager; import android.media.AudioManager;
import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.PopupWindow;
import com.kunzisoft.keepass_model.Entry; import com.kunzisoft.keepass.R;
import com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver; import com.kunzisoft.keepass.magikeyboard.adapter.FieldsAdapter;
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver;
import com.kunzisoft.keepass.magikeyboard.view.MagikeyboardView;
import com.kunzisoft.keepass.model.Entry;
import static com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION; import static com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
public class MagikIME extends InputMethodService public class MagikIME extends InputMethodService
implements KeyboardView.OnKeyboardActionListener { implements KeyboardView.OnKeyboardActionListener {
private static final String TAG = MagikIME.class.getName(); private static final String TAG = MagikIME.class.getName();
private static final int KEY_CHANGE_KEYBOARD = 600; public static final int KEY_BACK_KEYBOARD = 600;
public static final int KEY_CHANGE_KEYBOARD = 601;
private static final int KEY_UNLOCK = 610; private static final int KEY_UNLOCK = 610;
private static final int KEY_LOCK = 611; private static final int KEY_LOCK = 611;
private static final int KEY_ENTRY = 620; private static final int KEY_ENTRY = 620;
@@ -59,6 +70,9 @@ public class MagikIME extends InputMethodService
private KeyboardView keyboardView; private KeyboardView keyboardView;
private Keyboard keyboard; private Keyboard keyboard;
private Keyboard keyboard_entry; private Keyboard keyboard_entry;
private PopupWindow popupCustomKeys;
private FieldsAdapter fieldsAdapter;
private boolean playSoundDuringCLick;
private LockBroadcastReceiver lockBroadcastReceiver; private LockBroadcastReceiver lockBroadcastReceiver;
@@ -77,31 +91,63 @@ public class MagikIME extends InputMethodService
registerReceiver(lockBroadcastReceiver, new IntentFilter(LOCK_ACTION)); registerReceiver(lockBroadcastReceiver, new IntentFilter(LOCK_ACTION));
} }
@Override
public void onDestroy() {
unregisterReceiver(lockBroadcastReceiver);
super.onDestroy();
}
@Override @Override
public View onCreateInputView() { public View onCreateInputView() {
keyboardView = (KeyboardView) getLayoutInflater().inflate(R.layout.keyboard, null); keyboardView = (MagikeyboardView) getLayoutInflater().inflate(R.layout.keyboard_view, null);
keyboard = new Keyboard(this, R.xml.keyboard_password); keyboard = new Keyboard(this, R.xml.keyboard_password);
keyboard_entry = new Keyboard(this, R.xml.keyboard_password_entry); keyboard_entry = new Keyboard(this, R.xml.keyboard_password_entry);
assignKeyboardView(); assignKeyboardView();
keyboardView.setOnKeyboardActionListener(this); keyboardView.setOnKeyboardActionListener(this);
keyboardView.setPreviewEnabled(false); keyboardView.setPreviewEnabled(false);
keyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
Context context = getBaseContext();
View popupFieldsView = LayoutInflater.from(context)
.inflate(R.layout.keyboard_popup_fields, new FrameLayout(context));
if (popupCustomKeys != null)
popupCustomKeys.dismiss();
popupCustomKeys = new PopupWindow(context);
popupCustomKeys.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
popupCustomKeys.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
popupCustomKeys.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
popupCustomKeys.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
popupCustomKeys.setContentView(popupFieldsView);
RecyclerView recyclerView = popupFieldsView.findViewById(R.id.keyboard_popup_fields_list);
fieldsAdapter = new FieldsAdapter(this);
fieldsAdapter.setOnItemClickListener(item -> getCurrentInputConnection().commitText(item.getValue(), 1));
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true));
recyclerView.setAdapter(fieldsAdapter);
View closeView = popupFieldsView.findViewById(R.id.keyboard_popup_close);
closeView.setOnClickListener(v -> popupCustomKeys.dismiss());
// Define preferences
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
keyboardView.setHapticFeedbackEnabled(
sharedPreferences.getBoolean(
getString(R.string.keyboard_key_vibrate_key),
getResources().getBoolean(R.bool.keyboard_key_vibrate_default)));
playSoundDuringCLick = sharedPreferences.getBoolean(
getString(R.string.keyboard_key_sound_key),
getResources().getBoolean(R.bool.keyboard_key_sound_default));
return keyboardView; return keyboardView;
} }
private void assignKeyboardView() { private void assignKeyboardView() {
if (entryKey != null) { if (keyboardView != null) {
keyboardView.setKeyboard(keyboard_entry); if (entryKey != null) {
} else { if (keyboard_entry != null)
keyboardView.setKeyboard(keyboard); keyboardView.setKeyboard(keyboard_entry);
} else {
if (keyboard != null) {
dismissCustomKeys();
keyboardView.setKeyboard(keyboard);
}
}
} }
} }
@@ -127,9 +173,16 @@ public class MagikIME extends InputMethodService
@Override @Override
public void onKey(int primaryCode, int[] keyCodes) { public void onKey(int primaryCode, int[] keyCodes) {
InputConnection ic = getCurrentInputConnection(); InputConnection inputConnection = getCurrentInputConnection();
// Vibrate
keyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
// Play a sound
if (playSoundDuringCLick)
playClick(primaryCode);
switch(primaryCode){ switch(primaryCode){
case KEY_CHANGE_KEYBOARD: case KEY_BACK_KEYBOARD:
try { try {
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
assert imeManager != null; assert imeManager != null;
@@ -141,99 +194,74 @@ public class MagikIME extends InputMethodService
if (imeManager != null) if (imeManager != null)
imeManager.showInputMethodPicker(); imeManager.showInputMethodPicker();
} }
// TODO Add a long press to choose the keyboard
break; break;
case KEY_CHANGE_KEYBOARD:
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imeManager != null)
imeManager.showInputMethodPicker();
break;
case KEY_UNLOCK: case KEY_UNLOCK:
// TODO Unlock key // TODO Unlock key
break; break;
case KEY_ENTRY: case KEY_ENTRY:
deleteEntryKey(this);
Intent intent = new Intent(this, EntryRetrieverActivity.class); Intent intent = new Intent(this, EntryRetrieverActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); startActivity(intent);
break; break;
case KEY_LOCK: case KEY_LOCK:
deleteEntryKey(this); deleteEntryKey(this);
dismissCustomKeys();
break; break;
case KEY_USERNAME: case KEY_USERNAME:
if (entryKey != null) { if (entryKey != null) {
InputConnection inputConnection = getCurrentInputConnection();
inputConnection.commitText(entryKey.getUsername(), 1); inputConnection.commitText(entryKey.getUsername(), 1);
} }
break; break;
case KEY_PASSWORD: case KEY_PASSWORD:
if (entryKey != null) { if (entryKey != null) {
InputConnection inputConnection = getCurrentInputConnection();
inputConnection.commitText(entryKey.getPassword(), 1); inputConnection.commitText(entryKey.getPassword(), 1);
} }
break; break;
case KEY_URL: case KEY_URL:
if (entryKey != null) { if (entryKey != null) {
InputConnection inputConnection = getCurrentInputConnection();
inputConnection.commitText(entryKey.getUrl(), 1); inputConnection.commitText(entryKey.getUrl(), 1);
} }
break; break;
case KEY_FIELDS: case KEY_FIELDS:
// TODO Fields key fieldsAdapter.setFields(entryKey.getCustomFields());
popupCustomKeys.showAtLocation(keyboardView, Gravity.END | Gravity.TOP, 0, 0);
break;
case Keyboard.KEYCODE_DELETE : case Keyboard.KEYCODE_DELETE :
ic.deleteSurroundingText(1, 0); inputConnection.deleteSurroundingText(1, 0);
break; break;
case Keyboard.KEYCODE_DONE: case Keyboard.KEYCODE_DONE:
ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
break; break;
default: default:
} }
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(getString(R.string.key_vibrate_key), getResources().getBoolean(R.bool.key_vibrate_default)))
vibrate();
if (sharedPreferences.getBoolean(getString(R.string.key_sound_key), getResources().getBoolean(R.bool.key_sound_default)))
playClick(primaryCode);
} }
@Override @Override
public void onPress(int primaryCode) { public void onPress(int primaryCode) {}
}
@Override
public void onRelease(int primaryCode) {}
@Override @Override
public void onRelease(int primaryCode) { public void onText(CharSequence text) {}
}
@Override @Override
public void onText(CharSequence text) { public void swipeDown() {}
}
@Override @Override
public void swipeDown() { public void swipeLeft() {}
}
@Override @Override
public void swipeLeft() { public void swipeRight() {}
}
@Override @Override
public void swipeRight() { public void swipeUp() {}
}
@Override
public void swipeUp() {
}
private void vibrate() {
// TODO better vibration
/*
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(50);
}
}
*/
}
private void playClick(int keyCode){ private void playClick(int keyCode){
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE); AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
@@ -249,4 +277,18 @@ public class MagikIME extends InputMethodService
default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD); default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
} }
} }
private void dismissCustomKeys() {
if (popupCustomKeys != null)
popupCustomKeys.dismiss();
if (fieldsAdapter != null)
fieldsAdapter.clear();
}
@Override
public void onDestroy() {
dismissCustomKeys();
unregisterReceiver(lockBroadcastReceiver);
super.onDestroy();
}
} }

View File

@@ -0,0 +1,76 @@
package com.kunzisoft.keepass.magikeyboard.adapter;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.model.Field;
import java.util.ArrayList;
import java.util.List;
public class FieldsAdapter extends RecyclerView.Adapter<FieldsAdapter.FieldViewHolder> {
private LayoutInflater inflater;
private List<Field> fields;
private OnItemClickListener listener;
public FieldsAdapter(Context context) {
this.inflater = LayoutInflater.from(context);
this.fields = new ArrayList<>();
}
public void setFields(List<Field> fields) {
this.fields = fields;
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
@NonNull
@Override
public FieldViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false);
return new FieldViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull FieldViewHolder holder, int position) {
Field field = fields.get(position);
holder.name.setText(field.getName());
holder.bind(field, listener);
}
@Override
public int getItemCount() {
return fields.size();
}
public void clear() {
fields.clear();
}
public interface OnItemClickListener {
void onItemClick(Field item);
}
class FieldViewHolder extends RecyclerView.ViewHolder {
TextView name;
FieldViewHolder(View itemView) {
super(itemView);
name = itemView.findViewById(R.id.keyboard_popup_field_item_name);
}
void bind(final Field item, final OnItemClickListener listener) {
itemView.setOnClickListener(v -> listener.onItemClick(item));
}
}
}

View File

@@ -1,4 +1,4 @@
package com.kunzisoft.magikeyboard.receiver; package com.kunzisoft.keepass.magikeyboard.receiver;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;

View File

@@ -1,13 +1,13 @@
package com.kunzisoft.magikeyboard.receiver; package com.kunzisoft.keepass.magikeyboard.receiver;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import com.kunzisoft.magikeyboard.KeyboardEntryNotificationService; import com.kunzisoft.keepass.R;
import com.kunzisoft.magikeyboard.MagikIME; import com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService;
import com.kunzisoft.magikeyboard.R; import com.kunzisoft.keepass.magikeyboard.MagikIME;
public class NotificationDeleteBroadcastReceiver extends BroadcastReceiver { public class NotificationDeleteBroadcastReceiver extends BroadcastReceiver {
@@ -15,8 +15,8 @@ public class NotificationDeleteBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// Clear the entry if define in preferences // Clear the entry if define in preferences
SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPreferences.getBoolean(context.getString(R.string.notification_entry_clear_close_key), if (sharedPreferences.getBoolean(context.getString(R.string.keyboard_notification_entry_clear_close_key),
context.getResources().getBoolean(R.bool.notification_entry_clear_close_default))) { context.getResources().getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
MagikIME.deleteEntryKey(context); MagikIME.deleteEntryKey(context);
} }

View File

@@ -0,0 +1,39 @@
package com.kunzisoft.keepass.magikeyboard.view;
import android.content.Context;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import static com.kunzisoft.keepass.magikeyboard.MagikIME.KEY_BACK_KEYBOARD;
import static com.kunzisoft.keepass.magikeyboard.MagikIME.KEY_CHANGE_KEYBOARD;
public class MagikeyboardView extends KeyboardView {
public MagikeyboardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected boolean onLongPress(Keyboard.Key key) {
// TODO Action on long press
if (key.codes[0] == KEY_BACK_KEYBOARD) {
getOnKeyboardActionListener().onKey(KEY_CHANGE_KEYBOARD, null);
return true;
} else {
//Log.d("LatinKeyboardView", "KEY: " + key.codes[0]);
return super.onLongPress(key);
}
}
}

View File

@@ -1,11 +1,10 @@
package com.kunzisoft.keepass_model; package com.kunzisoft.keepass.model;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.util.HashMap; import java.util.ArrayList;
import java.util.Map; import java.util.List;
import java.util.Set;
public class Entry implements Parcelable { public class Entry implements Parcelable {
@@ -13,14 +12,14 @@ public class Entry implements Parcelable {
private String username; private String username;
private String password; private String password;
private String url; private String url;
private Map<String, String> customFields; private List<Field> customFields;
public Entry() { public Entry() {
this.title = ""; this.title = "";
this.username = ""; this.username = "";
this.password = ""; this.password = "";
this.url = ""; this.url = "";
this.customFields = new HashMap<>(); this.customFields = new ArrayList<>();
} }
protected Entry(Parcel in) { protected Entry(Parcel in) {
@@ -29,7 +28,7 @@ public class Entry implements Parcelable {
password = in.readString(); password = in.readString();
url = in.readString(); url = in.readString();
//noinspection unchecked //noinspection unchecked
customFields = in.readHashMap(String.class.getClassLoader()); customFields = in.readArrayList(Field.class.getClassLoader());
} }
public static final Creator<Entry> CREATOR = new Creator<Entry>() { public static final Creator<Entry> CREATOR = new Creator<Entry>() {
@@ -76,16 +75,12 @@ public class Entry implements Parcelable {
this.url = url; this.url = url;
} }
public Set<String> getCustomFieldsKeys() { public List<Field> getCustomFields() {
return customFields.keySet(); return customFields;
} }
public String getCustomField(String key) { public void addCustomField(Field field) {
return customFields.get(key); this.customFields.add(field);
}
public void setCustomField(String key, String value) {
this.customFields.put(key, value);
} }
@Override @Override
@@ -99,6 +94,6 @@ public class Entry implements Parcelable {
parcel.writeString(username); parcel.writeString(username);
parcel.writeString(password); parcel.writeString(password);
parcel.writeString(url); parcel.writeString(url);
parcel.writeMap(customFields); parcel.writeArray(customFields.toArray());
} }
} }

View File

@@ -0,0 +1,59 @@
package com.kunzisoft.keepass.model;
import android.os.Parcel;
import android.os.Parcelable;
public class Field implements Parcelable {
private String name;
private String value;
public Field(String name, String value) {
this.name = name;
this.value = value;
}
public Field(Parcel in) {
this.name = in.readString();
this.value = in.readString();
}
public static final Creator<Field> CREATOR = new Creator<Field>() {
@Override
public Field createFromParcel(Parcel in) {
return new Field(in);
}
@Override
public Field[] newArray(int size) {
return new Field[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(value);
}
}

View File

@@ -239,11 +239,13 @@ public class NotificationCopyingService extends Service {
} }
countingDownTask = null; countingDownTask = null;
notificationManager.cancel(myNotificationId); notificationManager.cancel(myNotificationId);
try { // Clean password only if no next field
clipboardHelper.cleanClipboard(); if (nextFields.size() <= 0)
} catch (SamsungClipboardException e) { try {
Log.e(TAG, "Clipboard can't be cleaned", e); clipboardHelper.cleanClipboard();
} } catch (SamsungClipboardException e) {
Log.e(TAG, "Clipboard can't be cleaned", e);
}
}); });
countingDownTask.start(); countingDownTask.start();

View File

@@ -56,8 +56,8 @@ import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView; import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.GroupActivity; import com.kunzisoft.keepass.activities.GroupActivity;
import com.kunzisoft.keepass.selection.EntrySelectionHelper; import com.kunzisoft.keepass.activities.IntentBuildLauncher;
import com.kunzisoft.keepass.lock.LockingActivity; import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper; import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.compat.ClipDataCompat; import com.kunzisoft.keepass.compat.ClipDataCompat;
@@ -69,6 +69,8 @@ import com.kunzisoft.keepass.fileselect.KeyFileHelper;
import com.kunzisoft.keepass.fingerprint.FingerPrintAnimatedVector; import com.kunzisoft.keepass.fingerprint.FingerPrintAnimatedVector;
import com.kunzisoft.keepass.fingerprint.FingerPrintExplanationDialog; import com.kunzisoft.keepass.fingerprint.FingerPrintExplanationDialog;
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper; import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.settings.PreferencesUtil; import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.stylish.StylishActivity; import com.kunzisoft.keepass.stylish.StylishActivity;
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment; import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment;
@@ -124,6 +126,8 @@ public class PasswordActivity extends StylishActivity
private CompoundButton checkboxDefaultDatabaseView; private CompoundButton checkboxDefaultDatabaseView;
private CompoundButton.OnCheckedChangeListener enableButtonOncheckedChangeListener; private CompoundButton.OnCheckedChangeListener enableButtonOncheckedChangeListener;
private boolean readOnly;
private DefaultCheckChange defaultCheckChange; private DefaultCheckChange defaultCheckChange;
private ValidateButtonViewClickListener validateButtonViewClickListener; private ValidateButtonViewClickListener validateButtonViewClickListener;
@@ -139,16 +143,23 @@ public class PasswordActivity extends StylishActivity
} }
public static void launch( public static void launch(
Activity act, Activity activity,
String fileName, String fileName,
String keyFile) throws FileNotFoundException { String keyFile) throws FileNotFoundException {
verifyFileNameUriFromLaunch(fileName); verifyFileNameUriFromLaunch(fileName);
Intent intent = new Intent(act, PasswordActivity.class); buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
// only to avoid visible flickering when redirecting
activity.startActivityForResult(intent, RESULT_CANCELED);
});
}
private static void buildAndLaunchIntent(Activity activity, String fileName, String keyFile,
IntentBuildLauncher intentBuildLauncher) {
Intent intent = new Intent(activity, PasswordActivity.class);
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName); intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile); intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
// only to avoid visible flickering when redirecting intentBuildLauncher.startActivityForResult(intent);
act.startActivityForResult(intent, RESULT_CANCELED);
} }
public static void launchForKeyboardResult( public static void launchForKeyboardResult(
@@ -158,17 +169,16 @@ public class PasswordActivity extends StylishActivity
} }
public static void launchForKeyboardResult( public static void launchForKeyboardResult(
Activity act, Activity activity,
String fileName, String fileName,
String keyFile) throws FileNotFoundException { String keyFile) throws FileNotFoundException {
verifyFileNameUriFromLaunch(fileName); verifyFileNameUriFromLaunch(fileName);
Intent intent = new Intent(act, PasswordActivity.class); buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName); EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile); // only to avoid visible flickering when redirecting
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent); activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
// only to avoid visible flickering when redirecting });
act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
} }
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
@@ -181,20 +191,19 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
public static void launchForAutofillResult( public static void launchForAutofillResult(
Activity act, Activity activity,
String fileName, String fileName,
String keyFile, String keyFile,
AssistStructure assistStructure) throws FileNotFoundException { AssistStructure assistStructure) throws FileNotFoundException {
verifyFileNameUriFromLaunch(fileName); verifyFileNameUriFromLaunch(fileName);
if ( assistStructure != null ) { if ( assistStructure != null ) {
Intent intent = new Intent(act, PasswordActivity.class); buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName); AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile); activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure); });
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
} else { } else {
launch(act, fileName, keyFile); launch(activity, fileName, keyFile);
} }
} }
@@ -243,7 +252,7 @@ public class PasswordActivity extends StylishActivity
case LockingActivity.RESULT_EXIT_LOCK: case LockingActivity.RESULT_EXIT_LOCK:
case Activity.RESULT_CANCELED: case Activity.RESULT_CANCELED:
setEmptyViews(); setEmptyViews();
App.getDB().clear(); App.getDB().clear(getApplicationContext());
break; break;
} }
} }
@@ -276,6 +285,8 @@ public class PasswordActivity extends StylishActivity
checkboxKeyfileView = findViewById(R.id.keyfile_checkox); checkboxKeyfileView = findViewById(R.id.keyfile_checkox);
checkboxDefaultDatabaseView = findViewById(R.id.default_database); checkboxDefaultDatabaseView = findViewById(R.id.default_database);
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState);
View browseView = findViewById(R.id.browse_button); View browseView = findViewById(R.id.browse_button);
keyFileHelper = new KeyFileHelper(PasswordActivity.this); keyFileHelper = new KeyFileHelper(PasswordActivity.this);
browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener()); browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener());
@@ -326,8 +337,6 @@ public class PasswordActivity extends StylishActivity
autofillHelper = new AutofillHelper(); autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent()); autofillHelper.retrieveAssistStructure(getIntent());
} }
checkAndPerformedEducation();
} }
@Override @Override
@@ -380,11 +389,17 @@ public class PasswordActivity extends StylishActivity
.execute(getIntent()); .execute(getIntent());
} }
@Override
protected void onSaveInstanceState(Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
/** /**
* Check and display learning views * Check and display learning views
* Displays the explanation for a database opening with fingerprints if available * Displays the explanation for a database opening with fingerprints if available
*/ */
private void checkAndPerformedEducation() { private void checkAndPerformedEducation(Menu menu) {
if (PreferencesUtil.isEducationScreensEnabled(this)) { if (PreferencesUtil.isEducationScreensEnabled(this)) {
if (!PreferencesUtil.isEducationUnlockPerformed(this)) { if (!PreferencesUtil.isEducationUnlockPerformed(this)) {
@@ -394,7 +409,7 @@ public class PasswordActivity extends StylishActivity
getString(R.string.education_unlock_title), getString(R.string.education_unlock_title),
getString(R.string.education_unlock_summary)) getString(R.string.education_unlock_summary))
.dimColor(R.color.green) .dimColor(R.color.green)
.icon(ContextCompat.getDrawable(this, R.mipmap.ic_launcher_round)) .icon(ContextCompat.getDrawable(getApplicationContext(), R.mipmap.ic_launcher_round))
.textColorInt(Color.WHITE) .textColorInt(Color.WHITE)
.tintTarget(false) .tintTarget(false)
.cancelable(true), .cancelable(true),
@@ -402,6 +417,44 @@ public class PasswordActivity extends StylishActivity
@Override @Override
public void onTargetClick(TapTargetView view) { public void onTargetClick(TapTargetView view) {
super.onTargetClick(view); super.onTargetClick(view);
performedReadOnlyEducation(menu);
}
@Override
public void onOuterCircleClick(TapTargetView view) {
super.onOuterCircleClick(view);
view.dismiss(false);
performedReadOnlyEducation(menu);
}
});
// TODO make a period for donation
PreferencesUtil.saveEducationPreference(PasswordActivity.this,
R.string.education_unlock_key);
}
}
}
/**
* Check and display learning views
* Displays read-only if available
*/
private void performedReadOnlyEducation(Menu menu) {
if (!PreferencesUtil.isEducationReadOnlyPerformed(this)) {
try {
TapTargetView.showFor(this,
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_open_file_read_mode_key,
getString(R.string.education_read_only_title),
getString(R.string.education_read_only_summary))
.textColorInt(Color.WHITE)
.tintTarget(true)
.cancelable(true),
new TapTargetView.Listener() {
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view);
MenuItem editItem = menu.findItem(R.id.menu_open_file_read_mode_key);
onOptionsItemSelected(editItem);
checkAndPerformedEducationForFingerprint(); checkAndPerformedEducationForFingerprint();
} }
@@ -410,11 +463,13 @@ public class PasswordActivity extends StylishActivity
super.onOuterCircleClick(view); super.onOuterCircleClick(view);
view.dismiss(false); view.dismiss(false);
checkAndPerformedEducationForFingerprint(); checkAndPerformedEducationForFingerprint();
} }
}); });
// TODO make a period for donation PreferencesUtil.saveEducationPreference(this,
PreferencesUtil.saveEducationPreference(PasswordActivity.this, R.string.education_unlock_key); R.string.education_read_only_key);
} catch (Exception e) {
// If icon not visible
Log.w(TAG, "Can't performed education for entry's edition");
} }
} }
} }
@@ -883,7 +938,7 @@ public class PasswordActivity extends StylishActivity
private void loadDatabase(String password, Uri keyfile) { private void loadDatabase(String password, Uri keyfile) {
// Clear before we load // Clear before we load
Database database = App.getDB(); Database database = App.getDB();
database.clear(); database.clear(getApplicationContext());
// Clear the shutdown flag // Clear the shutdown flag
App.clearShutdown(); App.clearShutdown();
@@ -953,14 +1008,14 @@ public class PasswordActivity extends StylishActivity
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure(); assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) { if (assistStructure != null) {
GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure); GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure, readOnly);
} }
} }
if (assistStructure == null) { if (assistStructure == null) {
if (entrySelectionMode) { if (entrySelectionMode) {
GroupActivity.launchForKeyboardResult(PasswordActivity.this); GroupActivity.launchForKeyboardResult(PasswordActivity.this, readOnly);
} else { } else {
GroupActivity.launch(PasswordActivity.this); GroupActivity.launch(PasswordActivity.this, readOnly);
} }
} }
} }
@@ -968,16 +1023,35 @@ public class PasswordActivity extends StylishActivity
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
// Read menu
inflater.inflate(R.menu.open_file, menu);
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key));
MenuUtil.defaultMenuInflater(inflater, menu); MenuUtil.defaultMenuInflater(inflater, menu);
// Fingerprint menu
if (!fingerprintMustBeConfigured if (!fingerprintMustBeConfigured
&& prefsNoBackup.contains(getPreferenceKeyValue()) ) && prefsNoBackup.contains(getPreferenceKeyValue()) )
inflater.inflate(R.menu.fingerprint, menu); inflater.inflate(R.menu.fingerprint, menu);
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
// Show education views
new Handler().post(() -> checkAndPerformedEducation(menu));
return true; return true;
} }
private void changeOpenFileReadIcon(MenuItem togglePassword) {
if ( readOnly ) {
togglePassword.setTitle(R.string.menu_file_selection_read_only);
togglePassword.setIcon(R.drawable.ic_read_only_white_24dp);
} else {
togglePassword.setTitle(R.string.menu_open_file_read_and_write);
togglePassword.setIcon(R.drawable.ic_read_write_white_24dp);
}
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
@@ -985,6 +1059,10 @@ public class PasswordActivity extends StylishActivity
case android.R.id.home: case android.R.id.home:
finish(); finish();
break; break;
case R.id.menu_open_file_read_mode_key:
readOnly = !readOnly;
changeOpenFileReadIcon(item);
break;
case R.id.menu_fingerprint_remove_key: case R.id.menu_fingerprint_remove_key:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEntryKey(); deleteEntryKey();

View File

@@ -1,107 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.search;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.ListNodesActivity;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.utils.MenuUtil;
import javax.annotation.Nullable;
public class SearchResultsActivity extends ListNodesActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutInflater().inflate(R.layout.search_results, null));
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.search_label));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
groupNameView = findViewById(R.id.group_name);
attachFragmentToContentView();
View notFoundView = findViewById(R.id.not_found_container);
View listContainer = findViewById(R.id.nodes_list_fragment_container);
if ( mCurrentGroup == null || mCurrentGroup.numbersOfChildEntries() < 1 ) {
listContainer.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listContainer.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
}
@Override
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
Database mDb = App.getDB();
// Likely the app has been killed exit the activity
if ( ! mDb.getLoaded() ) {
finish();
}
return mDb.search(getSearchStr(getIntent()).trim());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case android.R.id.home:
finish();
}
return super.onOptionsItemSelected(item);
}
private String getSearchStr(Intent queryIntent) {
// get and process search query here
final String queryAction = queryIntent.getAction();
if ( Intent.ACTION_SEARCH.equals(queryAction) ) {
return queryIntent.getStringExtra(SearchManager.QUERY);
}
return "";
}
}

View File

@@ -5,7 +5,8 @@ import android.content.Intent;
import android.util.Log; import android.util.Log;
import com.kunzisoft.keepass.database.PwEntry; import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass_model.Entry; import com.kunzisoft.keepass.model.Entry;
import com.kunzisoft.keepass.model.Field;
public class EntrySelectionHelper { public class EntrySelectionHelper {
@@ -37,9 +38,11 @@ public class EntrySelectionHelper {
entryModel.setUsername(entry.getUsername()); entryModel.setUsername(entry.getUsername());
entryModel.setPassword(entry.getPassword()); entryModel.setPassword(entry.getPassword());
entryModel.setUrl(entry.getUrl()); entryModel.setUrl(entry.getUrl());
// TODO Fields
if (entry.containsCustomFields()) { if (entry.containsCustomFields()) {
//entryModel.setCustomField(entry.getFields()); entry.getFields()
.doActionToAllCustomProtectedField(
(key, value) -> entryModel.addCustomField(
new Field(key, value.toString())));
} }
mReplyIntent.putExtra( mReplyIntent.putExtra(

View File

@@ -17,21 +17,16 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.magikeyboard.settings; package com.kunzisoft.keepass.settings;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import com.kunzisoft.magikeyboard.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.lock.LockingActivity;
import java.lang.reflect.Constructor; public class MagikIMESettings extends LockingActivity {
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MagikIMESettings extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -49,26 +44,6 @@ public class MagikIMESettings extends AppCompatActivity {
.replace(R.id.fragment_container, new MagikIMESettingsFragment()) .replace(R.id.fragment_container, new MagikIMESettingsFragment())
.commit(); .commit();
} }
// TODO Remove after all dev
try {
Class<?> underDevClass = Class.forName("com.kunzisoft.keepass.dialogs.UnderDevelopmentFeatureDialogFragment");
Constructor<?> constructor = underDevClass.getConstructor();
Object object = constructor.newInstance();
Method showMethod = underDevClass.getMethod("show", FragmentManager.class, String.class);
showMethod.invoke(object, getSupportFragmentManager(), "magikeyboard_dev_dialog");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} }
@Override @Override

View File

@@ -1,15 +1,15 @@
package com.kunzisoft.magikeyboard.settings; package com.kunzisoft.keepass.settings;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.PreferenceFragmentCompat;
import com.kunzisoft.magikeyboard.R; import com.kunzisoft.keepass.R;
public class MagikIMESettingsFragment extends PreferenceFragmentCompat { public class MagikIMESettingsFragment extends PreferenceFragmentCompat {
@Override @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
// Load the preferences from an XML resource // Load the preferences from an XML resource
setPreferencesFromResource(R.xml.ime_preferences, rootKey); setPreferencesFromResource(R.xml.keyboard_preferences, rootKey);
} }
} }

View File

@@ -43,6 +43,7 @@ import android.widget.Toast;
import com.kunzisoft.keepass.BuildConfig; import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.app.App; import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database; import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.dialogs.ProFeatureDialogFragment; import com.kunzisoft.keepass.dialogs.ProFeatureDialogFragment;
@@ -50,7 +51,7 @@ import com.kunzisoft.keepass.dialogs.UnavailableFeatureDialogFragment;
import com.kunzisoft.keepass.dialogs.UnderDevelopmentFeatureDialogFragment; import com.kunzisoft.keepass.dialogs.UnderDevelopmentFeatureDialogFragment;
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper; import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
import com.kunzisoft.keepass.icons.IconPackChooser; import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.keyboard.KeyboardExplanationDialog; import com.kunzisoft.keepass.dialogs.KeyboardExplanationDialogFragment;
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseDescriptionPreferenceDialogFragmentCompat; import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseDescriptionPreferenceDialogFragmentCompat;
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat; import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat;
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseKeyDerivationPreferenceDialogFragmentCompat; import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseKeyDerivationPreferenceDialogFragmentCompat;
@@ -59,7 +60,6 @@ import com.kunzisoft.keepass.settings.preferenceDialogFragment.MemoryUsagePrefer
import com.kunzisoft.keepass.settings.preferenceDialogFragment.ParallelismPreferenceDialogFragmentCompat; import com.kunzisoft.keepass.settings.preferenceDialogFragment.ParallelismPreferenceDialogFragmentCompat;
import com.kunzisoft.keepass.settings.preferenceDialogFragment.RoundsPreferenceDialogFragmentCompat; import com.kunzisoft.keepass.settings.preferenceDialogFragment.RoundsPreferenceDialogFragmentCompat;
import com.kunzisoft.keepass.stylish.Stylish; import com.kunzisoft.keepass.stylish.Stylish;
import com.kunzisoft.magikeyboard.settings.MagikIMESettings;
public class NestedSettingsFragment extends PreferenceFragmentCompat public class NestedSettingsFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceClickListener { implements Preference.OnPreferenceClickListener {
@@ -72,6 +72,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
private static final int REQUEST_CODE_AUTOFILL = 5201; private static final int REQUEST_CODE_AUTOFILL = 5201;
private Database database;
private boolean databaseReadOnly;
private int count = 0; private int count = 0;
private Preference roundPref; private Preference roundPref;
@@ -79,14 +82,24 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
private Preference parallelismPref; private Preference parallelismPref;
public static NestedSettingsFragment newInstance(Screen key) { public static NestedSettingsFragment newInstance(Screen key) {
return newInstance(key, ReadOnlyHelper.READ_ONLY_DEFAULT);
}
public static NestedSettingsFragment newInstance(Screen key, boolean databaseReadOnly) {
NestedSettingsFragment fragment = new NestedSettingsFragment(); NestedSettingsFragment fragment = new NestedSettingsFragment();
// supply arguments to bundle. // supply arguments to bundle.
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putInt(TAG_KEY, key.ordinal()); args.putInt(TAG_KEY, key.ordinal());
ReadOnlyHelper.putReadOnlyInBundle(args, databaseReadOnly);
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@@ -110,6 +123,10 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (getArguments() != null) if (getArguments() != null)
key = getArguments().getInt(TAG_KEY); key = getArguments().getInt(TAG_KEY);
database = App.getDB();
databaseReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
databaseReadOnly = database.isReadOnly() || databaseReadOnly;
// Load the preferences from an XML resource // Load the preferences from an XML resource
switch (Screen.values()[key]) { switch (Screen.values()[key]) {
case APPLICATION: case APPLICATION:
@@ -285,8 +302,8 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
Preference keyboardPreference = findPreference(getString(R.string.magic_keyboard_key)); Preference keyboardPreference = findPreference(getString(R.string.magic_keyboard_key));
keyboardPreference.setOnPreferenceClickListener(preference -> { keyboardPreference.setOnPreferenceClickListener(preference -> {
if (getFragmentManager() != null) { if (getFragmentManager() != null) {
KeyboardExplanationDialog fingerPrintDialog = new KeyboardExplanationDialog(); KeyboardExplanationDialogFragment keyboardDialog = new KeyboardExplanationDialogFragment();
fingerPrintDialog.show(getFragmentManager(), "keyboardExplanationDialog"); keyboardDialog.show(getFragmentManager(), "keyboardExplanationDialog");
} }
return false; return false;
}); });
@@ -306,23 +323,22 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
case DATABASE: case DATABASE:
setPreferencesFromResource(R.xml.database_preferences, rootKey); setPreferencesFromResource(R.xml.database_preferences, rootKey);
Database db = App.getDB(); if (database.getLoaded()) {
if (db.getLoaded()) {
PreferenceCategory dbGeneralPrefCategory = (PreferenceCategory) findPreference(getString(R.string.database_general_key)); PreferenceCategory dbGeneralPrefCategory = (PreferenceCategory) findPreference(getString(R.string.database_general_key));
// Db name // Db name
Preference dbNamePref = findPreference(getString(R.string.database_name_key)); Preference dbNamePref = findPreference(getString(R.string.database_name_key));
if ( db.containsName() ) { if ( database.containsName() ) {
dbNamePref.setSummary(db.getName()); dbNamePref.setSummary(database.getName());
} else { } else {
dbGeneralPrefCategory.removePreference(dbNamePref); dbGeneralPrefCategory.removePreference(dbNamePref);
} }
// Db description // Db description
Preference dbDescriptionPref = findPreference(getString(R.string.database_description_key)); Preference dbDescriptionPref = findPreference(getString(R.string.database_description_key));
if ( db.containsDescription() ) { if ( database.containsDescription() ) {
dbDescriptionPref.setSummary(db.getDescription()); dbDescriptionPref.setSummary(database.getDescription());
} else { } else {
dbGeneralPrefCategory.removePreference(dbDescriptionPref); dbGeneralPrefCategory.removePreference(dbDescriptionPref);
} }
@@ -331,9 +347,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
SwitchPreference recycleBinPref = (SwitchPreference) findPreference(getString(R.string.recycle_bin_key)); SwitchPreference recycleBinPref = (SwitchPreference) findPreference(getString(R.string.recycle_bin_key));
// TODO Recycle // TODO Recycle
dbGeneralPrefCategory.removePreference(recycleBinPref); // To delete dbGeneralPrefCategory.removePreference(recycleBinPref); // To delete
if (db.isRecycleBinAvailable()) { if (database.isRecycleBinAvailable()) {
recycleBinPref.setChecked(db.isRecycleBinEnabled()); recycleBinPref.setChecked(database.isRecycleBinEnabled());
recycleBinPref.setEnabled(false); recycleBinPref.setEnabled(false);
} else { } else {
dbGeneralPrefCategory.removePreference(recycleBinPref); dbGeneralPrefCategory.removePreference(recycleBinPref);
@@ -341,27 +357,27 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
// Version // Version
Preference dbVersionPref = findPreference(getString(R.string.database_version_key)); Preference dbVersionPref = findPreference(getString(R.string.database_version_key));
dbVersionPref.setSummary(db.getVersion()); dbVersionPref.setSummary(database.getVersion());
// Encryption Algorithm // Encryption Algorithm
Preference algorithmPref = findPreference(getString(R.string.encryption_algorithm_key)); Preference algorithmPref = findPreference(getString(R.string.encryption_algorithm_key));
algorithmPref.setSummary(db.getEncryptionAlgorithmName(getResources())); algorithmPref.setSummary(database.getEncryptionAlgorithmName(getResources()));
// Key derivation function // Key derivation function
Preference kdfPref = findPreference(getString(R.string.key_derivation_function_key)); Preference kdfPref = findPreference(getString(R.string.key_derivation_function_key));
kdfPref.setSummary(db.getKeyDerivationName(getResources())); kdfPref.setSummary(database.getKeyDerivationName(getResources()));
// Round encryption // Round encryption
roundPref = findPreference(getString(R.string.transform_rounds_key)); roundPref = findPreference(getString(R.string.transform_rounds_key));
roundPref.setSummary(db.getNumberKeyEncryptionRoundsAsString()); roundPref.setSummary(database.getNumberKeyEncryptionRoundsAsString());
// Memory Usage // Memory Usage
memoryPref = findPreference(getString(R.string.memory_usage_key)); memoryPref = findPreference(getString(R.string.memory_usage_key));
memoryPref.setSummary(db.getMemoryUsageAsString()); memoryPref.setSummary(database.getMemoryUsageAsString());
// Parallelism // Parallelism
parallelismPref = findPreference(getString(R.string.parallelism_key)); parallelismPref = findPreference(getString(R.string.parallelism_key));
parallelismPref.setSummary(db.getParallelismAsString()); parallelismPref.setSummary(database.getParallelismAsString());
} else { } else {
Log.e(getClass().getName(), "Database isn't ready"); Log.e(getClass().getName(), "Database isn't ready");
@@ -480,18 +496,16 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
assert getFragmentManager() != null; assert getFragmentManager() != null;
DialogFragment dialogFragment = null; boolean otherDialogFragment = false;
DialogFragment dialogFragment = null;
if (preference.getKey().equals(getString(R.string.database_name_key))) { if (preference.getKey().equals(getString(R.string.database_name_key))) {
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.getKey()); dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.getKey());
} } else if (preference.getKey().equals(getString(R.string.database_description_key))) {
else if (preference.getKey().equals(getString(R.string.database_description_key))) {
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.getKey()); dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} } else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.getKey()); dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} } else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
DatabaseKeyDerivationPreferenceDialogFragmentCompat keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.getKey()); DatabaseKeyDerivationPreferenceDialogFragmentCompat keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.getKey());
// Add other prefs to manage // Add other prefs to manage
if (roundPref != null) if (roundPref != null)
@@ -501,24 +515,23 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (parallelismPref != null) if (parallelismPref != null)
keyDerivationDialogFragment.setParallelismPreference(parallelismPref); keyDerivationDialogFragment.setParallelismPreference(parallelismPref);
dialogFragment = keyDerivationDialogFragment; dialogFragment = keyDerivationDialogFragment;
} } else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.getKey()); dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} } else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.getKey()); dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.getKey());
} } else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.getKey()); dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} else {
otherDialogFragment = true;
} }
if (dialogFragment != null) { if (dialogFragment != null && !databaseReadOnly) {
dialogFragment.setTargetFragment(this, 0); dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getFragmentManager(), null); dialogFragment.show(getFragmentManager(), null);
} }
// Could not be handled here. Try with the super method. // Could not be handled here. Try with the super method.
else { else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference); super.onDisplayPreferenceDialog(preference);
} }
} }
@@ -538,6 +551,12 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
} }
} }
@Override
public void onSaveInstanceState(Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, databaseReadOnly);
super.onSaveInstanceState(outState);
}
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
// TODO encapsulate // TODO encapsulate

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.settings;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.TypedValue;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.SortNodeEnum; import com.kunzisoft.keepass.database.SortNodeEnum;
@@ -54,9 +55,22 @@ public class PreferencesUtil {
sharedPreferencesEditor.apply(); sharedPreferencesEditor.apply();
} }
public static boolean showUsernamesListEntries(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.list_entries_show_username_key),
context.getResources().getBoolean(R.bool.list_entries_show_username_default));
}
/**
* Retrieve the text size in SP, verify the integrity of the size stored in preference
*/
public static float getListTextSize(Context ctx) { public static float getListTextSize(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return Float.parseFloat(prefs.getString(ctx.getString(R.string.list_size_key), ctx.getString(R.string.list_size_default))); String defaultSizeString = ctx.getString(R.string.list_size_default);
String listSize = prefs.getString(ctx.getString(R.string.list_size_key), defaultSizeString);
if (!Arrays.asList(ctx.getResources().getStringArray(R.array.list_size_values)).contains(listSize))
listSize = defaultSizeString;
return Float.parseFloat(listSize);
} }
public static int getDefaultPasswordLength(Context ctx) { public static int getDefaultPasswordLength(Context ctx) {
@@ -139,12 +153,26 @@ public class PreferencesUtil {
ctx.getResources().getBoolean(R.bool.auto_open_file_uri_default)); ctx.getResources().getBoolean(R.bool.auto_open_file_uri_default));
} }
public static boolean isFirstTimeAskAllowCopyPasswordAndProtectedFields(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_first_time_key),
ctx.getResources().getBoolean(R.bool.allow_copy_password_first_time_default));
}
public static boolean allowCopyPasswordAndProtectedFields(Context ctx) { public static boolean allowCopyPasswordAndProtectedFields(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_key), return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_key),
ctx.getResources().getBoolean(R.bool.allow_copy_password_default)); ctx.getResources().getBoolean(R.bool.allow_copy_password_default));
} }
public static void setAllowCopyPasswordAndProtectedFields(Context ctx, boolean allowCopy) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
prefs.edit()
.putBoolean(ctx.getString(R.string.allow_copy_password_first_time_key), false)
.putBoolean(ctx.getString(R.string.allow_copy_password_key), allowCopy)
.apply();
}
public static String getIconPackSelectedId(Context context) { public static String getIconPackSelectedId(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getString( return prefs.getString(
@@ -158,6 +186,12 @@ public class PreferencesUtil {
context.getResources().getBoolean(R.bool.allow_no_password_default)); context.getResources().getBoolean(R.bool.allow_no_password_default));
} }
public static boolean enableReadOnlyDatabase(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.enable_read_only_key),
context.getResources().getBoolean(R.bool.enable_read_only_default));
}
/** /**
* All preference keys associated with education * All preference keys associated with education
*/ */
@@ -166,6 +200,7 @@ public class PreferencesUtil {
R.string.education_select_db_key, R.string.education_select_db_key,
R.string.education_open_link_db_key, R.string.education_open_link_db_key,
R.string.education_unlock_key, R.string.education_unlock_key,
R.string.education_read_only_key,
R.string.education_search_key, R.string.education_search_key,
R.string.education_new_node_key, R.string.education_new_node_key,
R.string.education_sort_key, R.string.education_sort_key,
@@ -245,6 +280,18 @@ public class PreferencesUtil {
context.getResources().getBoolean(R.bool.education_unlock_default)); context.getResources().getBoolean(R.bool.education_unlock_default));
} }
/**
* Determines whether the explanatory view of the database read-only has already been displayed.
*
* @param context The context to open the SharedPreferences
* @return boolean value of education_read_only_key key
*/
public static boolean isEducationReadOnlyPerformed(Context context) {
SharedPreferences prefs = getEducationSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.education_read_only_key),
context.getResources().getBoolean(R.bool.education_read_only_default));
}
/** /**
* Determines whether the explanatory view of search has already been displayed. * Determines whether the explanatory view of search has already been displayed.
* *

View File

@@ -28,6 +28,7 @@ import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.lock.LockingActivity; import com.kunzisoft.keepass.lock.LockingActivity;
@@ -39,17 +40,18 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
private Toolbar toolbar; private Toolbar toolbar;
public static void launch(Activity activity) { public static void launch(Activity activity, boolean readOnly) {
Intent i = new Intent(activity, SettingsActivity.class); Intent intent = new Intent(activity, SettingsActivity.class);
activity.startActivity(i); ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
activity.startActivity(intent);
} }
public static void launch(Activity activity, boolean checkLock) { public static void launch(Activity activity, boolean readOnly, boolean checkLock) {
// To avoid flickering when launch settings in a LockingActivity // To avoid flickering when launch settings in a LockingActivity
if (!checkLock) if (!checkLock)
launch(activity); launch(activity, readOnly);
else if (LockingActivity.checkTimeIsAllowedOrFinish(activity)) { else if (LockingActivity.checkTimeIsAllowedOrFinish(activity)) {
launch(activity); launch(activity, readOnly);
} }
} }
@@ -114,7 +116,7 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right) R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED) .replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, readOnly), TAG_NESTED)
.addToBackStack(TAG_NESTED) .addToBackStack(TAG_NESTED)
.commit(); .commit();

View File

@@ -0,0 +1,11 @@
package com.kunzisoft.keepass.stream;
import java.io.IOException;
public interface ActionReadBytes {
/**
* Called after each buffer fill
* @param buffer filled
*/
void doAction(byte[] buffer) throws IOException;
}

View File

@@ -19,11 +19,9 @@
*/ */
package com.kunzisoft.keepass.stream; package com.kunzisoft.keepass.stream;
import com.kunzisoft.keepass.utils.Types;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.UUID; import java.util.Arrays;
/** Little endian version of the DataInputStream /** Little endian version of the DataInputStream
@@ -32,7 +30,7 @@ import java.util.UUID;
*/ */
public class LEDataInputStream extends InputStream { public class LEDataInputStream extends InputStream {
public static final long INT_TO_LONG_MASK = 0xffffffffL; private static final long INT_TO_LONG_MASK = 0xffffffffL;
private InputStream baseStream; private InputStream baseStream;
@@ -106,8 +104,9 @@ public class LEDataInputStream extends InputStream {
} }
public byte[] readBytes(int length) throws IOException { public byte[] readBytes(int length) throws IOException {
// TODO Exception max length < buffer size
byte[] buf = new byte[length]; byte[] buf = new byte[length];
int count = 0; int count = 0;
while ( count < length ) { while ( count < length ) {
int read = read(buf, count, length - count); int read = read(buf, count, length - count);
@@ -126,6 +125,33 @@ public class LEDataInputStream extends InputStream {
return buf; return buf;
} }
public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException {
int bufferSize = 256 * 3; // TODO Buffer size
byte[] buffer = new byte[bufferSize];
int offset = 0;
int read = 0;
while ( offset < length && read != -1) {
// To reduce the buffer for the last bytes reads
if (length - offset < bufferSize) {
bufferSize = length - offset;
buffer = new byte[bufferSize];
}
read = read(buffer, 0, bufferSize);
// To get only the bytes read
byte[] optimizedBuffer;
if (read >= 0 && buffer.length > read) {
optimizedBuffer = Arrays.copyOf(buffer, read);
} else {
optimizedBuffer = buffer;
}
actionReadBytes.doAction(optimizedBuffer);
offset += read;
}
}
public static int readUShort(InputStream is) throws IOException { public static int readUShort(InputStream is) throws IOException {
byte[] buf = new byte[2]; byte[] buf = new byte[2];
@@ -146,11 +172,11 @@ public class LEDataInputStream extends InputStream {
* @return * @return
*/ */
public static int readUShort( byte[] buf, int offset ) { public static int readUShort( byte[] buf, int offset ) {
return (buf[offset + 0] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8); return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8);
} }
public static long readLong( byte buf[], int offset ) { public static long readLong( byte buf[], int offset ) {
return ((long)buf[offset + 0] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8) return ((long)buf[offset] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8)
+ (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24) + (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24)
+ (((long)buf[offset + 4] & 0xFF) << 32) + (((long)buf[offset + 5] & 0xFF) << 40) + (((long)buf[offset + 4] & 0xFF) << 32) + (((long)buf[offset + 5] & 0xFF) << 40)
+ (((long)buf[offset + 6] & 0xFF) << 48) + (((long)buf[offset + 7] & 0xFF) << 56); + (((long)buf[offset + 6] & 0xFF) << 48) + (((long)buf[offset + 7] & 0xFF) << 56);
@@ -180,14 +206,8 @@ public class LEDataInputStream extends InputStream {
* @return * @return
*/ */
public static int readInt( byte buf[], int offset ) { public static int readInt( byte buf[], int offset ) {
return (buf[offset + 0] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8) + ((buf[offset + 2] & 0xFF) << 16) return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8) + ((buf[offset + 2] & 0xFF) << 16)
+ ((buf[offset + 3] & 0xFF) << 24); + ((buf[offset + 3] & 0xFF) << 24);
} }
public UUID readUUID() throws IOException {
byte[] buf = readBytes(16);
return Types.bytestoUUID(buf);
}
} }

View File

@@ -88,8 +88,8 @@ public class ProgressTaskDialogFragment extends DialogFragment implements Progre
@Override @Override
public void onDismiss(DialogInterface dialog) { public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
Util.unlockScreenOrientation(getActivity()); Util.unlockScreenOrientation(getActivity());
super.onDismiss(dialog);
} }
public static void stop(AppCompatActivity activity) { public static void stop(AppCompatActivity activity) {

View File

@@ -31,8 +31,9 @@ public class TimeoutHelper {
private static final String TAG = "TimeoutHelper"; private static final String TAG = "TimeoutHelper";
private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes public static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
public static final long TIMEOUT_NEVER = -1; // Infinite
public static void recordTime(Activity act) { public static void recordTime(Activity act) {
// Record timeout time in case timeout service is killed // Record timeout time in case timeout service is killed
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
@@ -56,9 +57,9 @@ public class TimeoutHelper {
long cur_time = System.currentTimeMillis(); long cur_time = System.currentTimeMillis();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(act); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(act);
long timeout_start = prefs.getLong(act.getString(R.string.timeout_key), -1); long timeout_start = prefs.getLong(act.getString(R.string.timeout_key), TIMEOUT_NEVER);
// The timeout never started // The timeout never started
if (timeout_start == -1) { if (timeout_start == TIMEOUT_NEVER) {
return true; return true;
} }
@@ -71,7 +72,7 @@ public class TimeoutHelper {
} }
// We are set to never timeout // We are set to never timeout
if (timeout == -1) { if (timeout == TIMEOUT_NEVER) {
return true; return true;
} }

View File

@@ -19,32 +19,195 @@
*/ */
package com.kunzisoft.keepass.utils; package com.kunzisoft.keepass.utils;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.util.Log;
import com.kunzisoft.keepass.stream.ActionReadBytes;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
public class MemUtil { public class MemUtil {
private static final String TAG = MemUtil.class.getName();
public static final int BUFFER_SIZE_BYTES = 3 * 128;
public static void copyStream(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[BUFFER_SIZE_BYTES];
int read;
try {
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
} catch (OutOfMemoryError error) {
throw new IOException(error);
}
}
public static void readBytes(@NonNull InputStream inputStream, ActionReadBytes actionReadBytes)
throws IOException {
byte[] buffer = new byte[MemUtil.BUFFER_SIZE_BYTES];
int read = 0;
while (read != -1) {
read = inputStream.read(buffer, 0, buffer.length);
if (read != -1) {
byte[] optimizedBuffer;
if (buffer.length == read) {
optimizedBuffer = buffer;
} else {
optimizedBuffer = Arrays.copyOf(buffer, read);
}
actionReadBytes.doAction(optimizedBuffer);
}
}
/*
byte[] buffer = new byte[BUFFER_SIZE_BYTES];
// To create the last buffer who is smaller
long numberOfFullBuffer = length / buffer.length;
long sizeOfFullBuffers = numberOfFullBuffer * buffer.length;
int read = 0;
//if (protectedBinary.length() > 0) {
while (read < length) {
// Create the last smaller buffer
if (read >= sizeOfFullBuffers)
buffer = new byte[(int) (length % buffer.length)];
read += inputStream.read(buffer, 0, buffer.length);
actionReadBytes.doAction(buffer);
}
//*/
}
public static byte[] decompress(byte[] input) throws IOException { public static byte[] decompress(byte[] input) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(input); ByteArrayInputStream bais = new ByteArrayInputStream(input);
GZIPInputStream gzis = new GZIPInputStream(bais); GZIPInputStream gzis = new GZIPInputStream(bais);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
Util.copyStream(gzis, baos); copyStream(gzis, baos);
return baos.toByteArray();
}
public static byte[] compress(byte[] input) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(input);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos);
Util.copyStream(bais, gzos);
gzos.close();
return baos.toByteArray(); return baos.toByteArray();
} }
public static byte[] compress(byte[] input) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(input);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos);
copyStream(bais, gzos);
gzos.close();
return baos.toByteArray();
}
/**
* Compresses the input data using GZip and outputs the compressed data.
*
* @param input
* An {@link InputStream} containing the input raw data.
*
* @return An {@link InputStream} to the compressed data.
*/
public static InputStream compress(final InputStream input) {
final PipedInputStream compressedDataStream = new PipedInputStream(3 * 128);
Log.d(TAG, "About to compress input data using gzip asynchronously...");
PipedOutputStream compressionOutput;
GZIPOutputStream gzipCompressedDataStream = null;
try {
compressionOutput = new PipedOutputStream(compressedDataStream);
gzipCompressedDataStream = new GZIPOutputStream(compressionOutput);
IOUtils.copy(input, gzipCompressedDataStream);
Log.e(TAG, "Successfully compressed input data using gzip.");
} catch (IOException e) {
Log.e(TAG, "Failed to compress input data.", e);
} finally {
if (gzipCompressedDataStream != null) {
try {
gzipCompressedDataStream.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close gzip output stream.", e);
}
}
}
return compressedDataStream;
}
// For writing to a Parcel
public static <K extends Parcelable,V extends Parcelable> void writeParcelableMap(
Parcel parcel, int flags, Map<K, V > map) {
parcel.writeInt(map.size());
for(Map.Entry<K, V> e : map.entrySet()){
parcel.writeParcelable(e.getKey(), flags);
parcel.writeParcelable(e.getValue(), flags);
}
}
// For reading from a Parcel
public static <K extends Parcelable,V extends Parcelable> Map<K,V> readParcelableMap(
Parcel parcel, Class<K> kClass, Class<V> vClass) {
int size = parcel.readInt();
Map<K, V> map = new HashMap<K, V>(size);
for(int i = 0; i < size; i++){
map.put(kClass.cast(parcel.readParcelable(kClass.getClassLoader())),
vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
}
return map;
}
// For writing map with string key to a Parcel
public static <V extends Parcelable> void writeStringParcelableMap(
Parcel parcel, int flags, Map<String, V> map) {
parcel.writeInt(map.size());
for(Map.Entry<String, V> e : map.entrySet()){
parcel.writeString(e.getKey());
parcel.writeParcelable(e.getValue(), flags);
}
}
// For reading map with string key from a Parcel
public static <V extends Parcelable> HashMap<String,V> readStringParcelableMap(
Parcel parcel, Class<V> vClass) {
int size = parcel.readInt();
HashMap<String, V> map = new HashMap<>(size);
for(int i = 0; i < size; i++){
map.put(parcel.readString(),
vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
}
return map;
}
// For writing map with string key and string value to a Parcel
public static void writeStringParcelableMap(Parcel dest, Map<String, String> map) {
dest.writeInt(map.size());
for(Map.Entry<String, String> e : map.entrySet()){
dest.writeString(e.getKey());
dest.writeString(e.getValue());
}
}
// For reading map with string key and string value from a Parcel
public static HashMap<String, String> readStringParcelableMap(Parcel in) {
int size = in.readInt();
HashMap<String, String> map = new HashMap<>(size);
for(int i = 0; i < size; i++){
map.put(in.readString(),
in.readString());
}
return map;
}
} }

View File

@@ -32,6 +32,8 @@ import com.kunzisoft.keepass.activities.AboutActivity;
import com.kunzisoft.keepass.settings.SettingsActivity; import com.kunzisoft.keepass.settings.SettingsActivity;
import com.kunzisoft.keepass.stylish.StylishActivity; import com.kunzisoft.keepass.stylish.StylishActivity;
import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
public class MenuUtil { public class MenuUtil {
@@ -56,20 +58,20 @@ public class MenuUtil {
} }
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) { public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) {
return onDefaultMenuOptionsItemSelected(activity, item, false); return onDefaultMenuOptionsItemSelected(activity, item, READ_ONLY_DEFAULT, false);
} }
/* /*
* @param checkLock Check the time lock before launch settings in LockingActivity * @param checkLock Check the time lock before launch settings in LockingActivity
*/ */
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean checkLock) { public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean readOnly, boolean checkLock) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_contribute: case R.id.menu_contribute:
return onContributionItemSelected(activity); return onContributionItemSelected(activity);
case R.id.menu_app_settings: case R.id.menu_app_settings:
// To avoid flickering when launch settings in a LockingActivity // To avoid flickering when launch settings in a LockingActivity
SettingsActivity.launch(activity, checkLock); SettingsActivity.launch(activity, readOnly, checkLock);
return true; return true;
case R.id.menu_about: case R.id.menu_about:

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