Compare commits

...

321 Commits
2.5RC1 ... 2.7

Author SHA1 Message Date
J-Jamet
dae19bbccf Merge branch 'release/2.7' 2020-06-18 14:59:28 +02:00
J-Jamet
c81f83887e Replace <strong> tags 2020-06-18 14:44:50 +02:00
J-Jamet
04e555dde9 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-06-18 14:25:28 +02:00
J-Jamet
be94518e31 Upgrade CHANGELOG 2020-06-18 14:25:06 +02:00
J-Jamet
66bec1e08c Merge branch 'feature/Button_Block_Autofill' into develop 2020-06-18 14:14:58 +02:00
J-Jamet
f61ce10716 Fix action bar style 2020-06-17 23:23:57 +02:00
J-Jamet
b1b92b2995 Add only the current app id in blocklist 2020-06-17 23:06:04 +02:00
J-Jamet
bd9f2c4757 Check if the search element is blocked after Autofill popup click 2020-06-17 22:49:09 +02:00
abidin toumi
a3ead2153e Translated using Weblate (Arabic)
Currently translated at 51.2% (222 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2020-06-17 22:41:44 +02:00
J-Jamet
e12f008b92 Merge branch 'develop' into feature/Button_Block_Autofill 2020-06-17 22:16:08 +02:00
J-Jamet
d064ece0ff Fix view flickering 2020-06-17 22:13:18 +02:00
J-Jamet
379fbf68b1 Fix special mode title 2020-06-17 21:54:59 +02:00
J-Jamet
83783c1a88 Better special mode implementation 2020-06-17 21:49:20 +02:00
J-Jamet
c7e46205b3 Encapsulate special mode view and add blocking menu for autofill 2020-06-17 21:29:28 +02:00
J-Jamet
0c61f0ded2 Better search info title 2020-06-17 20:22:17 +02:00
J-Jamet
49e2ec0498 Toolbar as SpecialModeView, show title 2020-06-17 20:01:45 +02:00
ihor_ck
fb0a74c101 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-06-16 20:41:43 +02:00
Renann Prado
245a7ddfe9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2020-06-14 23:19:30 +02:00
Stephan Paternotte
ca8874c2e1 Translated using Weblate (Dutch)
Currently translated at 99.5% (431 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-06-14 23:19:29 +02:00
Oliver
dbcd7c8e03 Translated using Weblate (German)
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-06-14 23:19:29 +02:00
J-Jamet
9cce63659e Fix selection mode and search with autofill 2020-06-13 23:18:39 +02:00
J-Jamet
be0bbab0c8 Add default values in Blocklists and input password as autofill hints 2020-06-13 17:01:59 +02:00
J-Jamet
7b6d3698c4 Add inputType as Hex 2020-06-13 15:20:24 +02:00
J-Jamet
56daca8b4f Fix default entry icon #589 2020-06-12 20:17:01 +02:00
J-Jamet
ed382d102e Fix backstack in selection mode, add cancel button #589 2020-06-12 19:57:01 +02:00
Filip Miletic
745de2502e Translated using Weblate (Croatian)
Currently translated at 71.5% (310 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-06-11 04:41:42 +02:00
zeritti
503a4b1374 Translated using Weblate (Czech)
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-06-11 04:41:42 +02:00
J-Jamet
0b71b2d659 Fix populate Magikeyboard when URL is present 2020-06-10 20:50:55 +02:00
J-Jamet
2ad244df94 Update CHANGELOG 2020-06-10 20:43:16 +02:00
J-Jamet
d0da4f03a6 Search through subdomains as parameter #584 2020-06-10 20:40:01 +02:00
J-Jamet
ab0cd4152a Fix search and autofill performance 2020-06-10 20:25:54 +02:00
J-Jamet
93d04bfe60 Fix crash 2020-06-10 20:20:18 +02:00
J-Jamet
b1ccb40bd3 Upgrade CHANGELOG 2020-06-10 19:08:19 +02:00
J-Jamet
d177001ea8 Upgrade CHANGELOG 2020-06-10 19:03:28 +02:00
J-Jamet
2f921897c7 Block list to blocklist 2020-06-10 18:58:40 +02:00
J-Jamet
1a0f7146ce Merge branch 'feature/Autofill_Blocklist' #571 2020-06-10 18:53:53 +02:00
J-Jamet
dff2386594 Fix shrink 2020-06-10 18:47:10 +02:00
J-Jamet
55cc782cc6 Keep list during orientation change 2020-06-10 18:33:22 +02:00
J-Jamet
6903099873 Better block list algorithm 2020-06-10 18:16:37 +02:00
J-Jamet
6e2d84be33 Dialog input much better 2020-06-10 18:02:52 +02:00
J-Jamet
9e542d0bbe Dialog input much better 2020-06-10 17:52:35 +02:00
J-Jamet
ade9af9ecd Block list verification 2020-06-10 16:43:23 +02:00
J-Jamet
59f11a1b26 Distinct block lists for AppId and WebDomain 2020-06-10 15:40:04 +02:00
Stephan Paternotte
cc1d6e2b47 Translated using Weblate (Dutch)
Currently translated at 99.5% (431 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-06-09 22:41:42 +02:00
J-Jamet
2655b1b3d1 Fix blocklist views 2020-06-09 20:15:27 +02:00
J-Jamet
053dd28f8c Rename to blocklist and fix persistent element 2020-06-09 20:07:34 +02:00
J-Jamet
65e4cf83d8 First commit for autofill blocklist 2020-06-09 18:35:40 +02:00
Oğuz Ersen
419099318c Translated using Weblate (Turkish)
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-06-08 02:41:43 +02:00
Allan Nordhøy
972edd3a30 Translated using Weblate (Norwegian Bokmål)
Currently translated at 80.3% (348 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-06-08 02:41:42 +02:00
Eric
cc4125e766 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-06-08 02:41:42 +02:00
ihor_ck
da40cc9830 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-06-08 02:41:41 +02:00
solokot
29d7e2dcfe Translated using Weblate (Russian)
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-06-06 11:58:57 +02:00
WaldiS
3d906fd582 Translated using Weblate (Polish)
Currently translated at 100.0% (433 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-06-06 11:58:56 +02:00
jan madsen
0cad43c18b Translated using Weblate (Danish)
Currently translated at 98.6% (427 of 433 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-06-06 11:58:56 +02:00
Hosted Weblate
c66b686a63 Merge branch 'origin/master' into Weblate. 2020-06-06 08:22:05 +02:00
J-Jamet
e2a1e3f327 Fix autofill for chrome 83.0.4103.96 #551 2020-06-05 22:56:00 +02:00
J-Jamet
2b5cf75a53 Update to 2.7 2020-06-05 15:46:25 +02:00
J-Jamet
1c350ac87b Merge tag '2.6' into develop
2.6
2020-06-05 14:31:39 +02:00
J-Jamet
ef6539909c Merge branch 'release/2.6' 2020-06-05 14:31:30 +02:00
J-Jamet
47d7c6fe65 Fix could not read credentials 2020-06-05 14:04:30 +02:00
J-Jamet
c94c5fbc95 Replace KeePass DX by KeePassDX 2020-06-05 13:20:09 +02:00
J-Jamet
63a833d114 Remove beta_dontask 2020-06-05 13:12:03 +02:00
J-Jamet
72162128e2 Fix strings 2020-06-05 12:56:48 +02:00
Kunzisoft
a8f712c335 Translated using Weblate (Telugu)
Currently translated at 1.8% (8 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/te/
2020-06-05 12:41:42 +02:00
Kunzisoft
ea8888a685 Translated using Weblate (Galician)
Currently translated at 6.7% (29 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2020-06-05 12:41:42 +02:00
Kunzisoft
2aecf69b67 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 14.5% (63 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nn/
2020-06-05 12:41:42 +02:00
Kunzisoft
1a4c24dd86 Translated using Weblate (Latvian)
Currently translated at 16.2% (70 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lv/
2020-06-05 12:41:41 +02:00
Kunzisoft
6cf2b45051 Translated using Weblate (Lithuanian)
Currently translated at 12.7% (55 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2020-06-05 12:41:41 +02:00
Kunzisoft
38dd2bdf6e Translated using Weblate (Finnish)
Currently translated at 20.1% (87 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fi/
2020-06-05 12:41:41 +02:00
Kunzisoft
8784f1da70 Translated using Weblate (Basque)
Currently translated at 15.7% (68 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2020-06-05 12:41:41 +02:00
Kunzisoft
b9208ea94e Translated using Weblate (Catalan)
Currently translated at 59.7% (258 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-06-05 12:41:41 +02:00
J-Jamet
dfb648b480 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-06-05 11:31:40 +02:00
J-Jamet
f7af4f06ea Add short Toast after populate Magikeyboard 2020-06-03 19:24:05 +02:00
Destiny Li
d98f9eb62c Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-06-03 08:41:41 +02:00
Jörgen Sjöbom
fef1335f51 Translated using Weblate (Swedish)
Currently translated at 85.8% (371 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-06-03 08:41:41 +02:00
J. Lavoie
59f14cbdd4 Translated using Weblate (Italian)
Currently translated at 96.2% (416 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-06-03 08:41:40 +02:00
J. Lavoie
e8645c543f Translated using Weblate (French)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-06-03 08:41:39 +02:00
Filippo De Bortoli
b92f6177e3 Translated using Weblate (Italian)
Currently translated at 95.6% (413 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-06-02 07:41:30 +02:00
J. Lavoie
2888829a4f Translated using Weblate (Italian)
Currently translated at 95.6% (413 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-06-02 07:41:30 +02:00
J-Jamet
3d392ead19 Try to fix biometric prompt flickering 2020-06-01 21:16:58 +02:00
J-Jamet
a79b2bfa79 Merge branch 'develop' into TacoTheDank-develop 2020-06-01 13:59:56 +02:00
J-Jamet
15e1e2f02e Remove apache commons-io lib dependencies 2020-06-01 13:55:17 +02:00
J-Jamet
9e652803d0 Merge branch 'develop' of git://github.com/TacoTheDank/KeePassDX into TacoTheDank-develop 2020-06-01 12:07:10 +02:00
J-Jamet
7795fceb72 Merge branch 'mattiasduerrmeier-master' into develop 2020-05-31 16:32:05 +02:00
J-Jamet
9f01f26ea6 Merge branch 'master' of git://github.com/mattiasduerrmeier/KeePassDX into mattiasduerrmeier-master 2020-05-31 16:31:07 +02:00
TacoTheDank
abbc9a1d7a Update firamono font 2020-05-29 22:53:53 -04:00
TacoTheDank
2c15befb8e Update many dependencies 2020-05-29 22:35:53 -04:00
TacoTheDank
ba8ea84c4e Update Studio, use Kotlin JDK8 2020-05-29 21:25:59 -04:00
TacoTheDank
97912046da Update gradle wrapper 2020-05-29 21:21:57 -04:00
mattiasduerrmeier
43e99c23e3 Update strings.xml 2020-05-29 10:35:55 +02:00
mattiasduerrmeier
ffe14d75fc update : delete password description
I didn't quite understand what the delete password settings does, and I found the description not to be complete.
I think changing the description could make it clearer. 
I thought about adding "to an existing database", but I wanted to keep it short.
2020-05-28 15:40:25 +02:00
ButterflyOfFire
872ef66641 Translated using Weblate (Arabic)
Currently translated at 50.0% (216 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2020-05-28 10:41:37 +02:00
J-Jamet
6363862ec2 Fix Autofill for in-app purchases #529 2020-05-27 13:48:46 +02:00
J-Jamet
775a112e83 Fix Magikeyboard delete key 2020-05-27 13:01:06 +02:00
J-Jamet
7168904290 Fix node selection color 2020-05-27 12:28:58 +02:00
Stephan Paternotte
4213209b08 Translated using Weblate (Dutch)
Currently translated at 99.5% (430 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-05-27 08:41:37 +02:00
Oymate
5dce91b7f6 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 4.6% (20 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn_BD/
2020-05-25 23:17:27 +02:00
Eric
6d7fb9f87c Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-05-25 23:17:26 +02:00
ihor_ck
cc6c9dd8d1 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-25 23:17:26 +02:00
Allan Nordhøy
21ebec52f3 Translated using Weblate (Greek)
Currently translated at 99.3% (429 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-05-25 23:17:26 +02:00
C. Rüdinger
11023ab225 Translated using Weblate (German)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-05-25 23:17:26 +02:00
scootergrisen
fe97b15905 Translated using Weblate (Danish)
Currently translated at 97.6% (422 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-05-25 23:17:25 +02:00
J-Jamet
de8738aa03 Default group icon for a new entry #293 2020-05-25 19:36:36 +02:00
J-Jamet
568bbf9126 Upgrade CHANGELOG 2020-05-25 19:06:57 +02:00
J-Jamet
a2481652da Better Autofill recognition #564 2020-05-25 19:03:33 +02:00
Aurel F
33e3d3272d Translated using Weblate (Romanian)
Currently translated at 92.8% (401 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2020-05-24 11:41:46 +02:00
Stephan Paternotte
b909651c29 Translated using Weblate (Dutch)
Currently translated at 99.5% (430 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2020-05-24 11:41:45 +02:00
J-Jamet
16794c5252 Merge branch 'feature/ShareTarget' into develop 2020-05-23 18:23:42 +02:00
J-Jamet
49ad270d88 Remove keyboard entry when no info to fill
Remove toast
2020-05-23 18:17:01 +02:00
J-Jamet
df9b6cb7e8 Revert "Fix back stack"
This reverts commit 797dc706e2.
2020-05-23 15:57:39 +02:00
J-Jamet
c6c4551928 Fix small string 2020-05-23 15:45:56 +02:00
J-Jamet
de84353eb0 Add MagikeyboardLauncherActivity 2020-05-23 15:34:04 +02:00
J-Jamet
6f05b80f34 Remove exported for EntrySelectionLauncherActivity 2020-05-23 15:02:45 +02:00
J-Jamet
797dc706e2 Fix back stack 2020-05-23 15:01:34 +02:00
J-Jamet
bdb615bcf9 Fix readonly and search after orientation change 2020-05-23 14:12:33 +02:00
J-Jamet
1b98717b0e Fix readonly and search after orientation change 2020-05-23 13:59:24 +02:00
J-Jamet
7bd1aedada Search improvement 2020-05-23 12:59:05 +02:00
J-Jamet
34bf8f9d1f Fix flickering during share 2020-05-23 10:48:36 +02:00
J-Jamet
b07f70e9fe Fix Share search without Magikeyboard 2020-05-22 22:01:10 +02:00
J-Jamet
e635788955 Add standard search 2020-05-22 13:22:09 +02:00
ihor_ck
bdb1cef3e5 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-22 11:58:34 +02:00
zeritti
e5a32e65c0 Translated using Weblate (Czech)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-05-22 11:58:33 +02:00
J-Jamet
5fc77922b4 Auto search entry by share to populate keyboard 2020-05-21 14:54:14 +02:00
J-Jamet
ce8add2895 Upgrade CHANGELOG 2020-05-21 12:29:28 +02:00
J-Jamet
88e1e5b770 Fix background theme color 2020-05-21 11:52:06 +02:00
J-Jamet
76ecbd3497 Revert NotificationService to keep notification in API23 2020-05-21 11:39:17 +02:00
J-Jamet
e33c9b932f LockReceiver in onCreate NotificationService 2020-05-20 20:00:12 +02:00
J-Jamet
038f6caa04 Fix notification hide 2020-05-20 19:01:19 +02:00
Oymate
36f5249d71 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 0.9% (4 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn_BD/
2020-05-20 17:41:38 +02:00
Oğuz Ersen
570702a5bd Translated using Weblate (Turkish)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-05-20 17:41:38 +02:00
Andrew
5d03c9c644 Translated using Weblate (English)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-05-20 17:41:38 +02:00
Dwhite Reeves
83906def4a Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (431 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-05-20 17:41:37 +02:00
ihor_ck
33e0f25fb1 Translated using Weblate (Ukrainian)
Currently translated at 99.7% (431 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-20 17:41:37 +02:00
solokot
2080de4139 Translated using Weblate (Russian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-05-20 17:41:36 +02:00
WaldiS
decaca889c Translated using Weblate (Polish)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-05-20 17:41:36 +02:00
Anonymous
794a39032f Translated using Weblate (Finnish)
Currently translated at 20.1% (87 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fi/
2020-05-20 17:41:36 +02:00
Jami Kettunen
cc3cf17060 Translated using Weblate (Finnish)
Currently translated at 20.1% (87 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fi/
2020-05-20 17:41:35 +02:00
C. Rüdinger
b07da4bfa8 Translated using Weblate (German)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-05-20 17:41:35 +02:00
J-Jamet
4f52a4ee79 Fix biometric prompt - better code implementation #554 2020-05-20 17:30:50 +02:00
J-Jamet
cf77b999a3 Fix biometric prompt auto open after orientation change #554 2020-05-20 16:24:06 +02:00
J-Jamet
c9f062bdd9 Fix biometric prompt auto open after activity result #554 2020-05-20 15:27:34 +02:00
J-Jamet
12669f7ea0 Fix biometric prompt auto open after lock #554 2020-05-20 15:19:10 +02:00
J-Jamet
03451d2a6e Remove unused @JvmOverloads 2020-05-20 13:31:23 +02:00
J-Jamet
b5d7595f87 Try to fix service bug 2020-05-20 12:03:54 +02:00
J-Jamet
0d3d760a43 Try to fix custom field bug 2020-05-20 11:36:19 +02:00
J-Jamet
1cfc86e5ce Fix auto biometric prompt in background #554 2020-05-20 11:09:45 +02:00
J-Jamet
938343323d Shorter message for invalid credentials 2020-05-19 21:44:40 +02:00
Oymate
37112715c0 Added translation using Weblate (Bengali (Bangladesh)) 2020-05-19 17:08:25 +02:00
Andrew
bc80c10193 Translated using Weblate (Russian)
Currently translated at 99.7% (431 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-05-18 13:56:29 +02:00
J-Jamet
d989f48226 Fix unwanted opening of the dialog task #187 2020-05-17 14:19:52 +02:00
J-Jamet
45bcbb3a3d Fix entry not visually deleted in search #559 2020-05-16 19:06:30 +02:00
Oğuz Ersen
f4a74e0254 Translated using Weblate (Turkish)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-05-16 06:41:36 +02:00
Ashley Ouding
ace83cf68d Translated using Weblate (English)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-05-16 06:41:36 +02:00
Eric
209b9b9a6f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-05-16 06:41:36 +02:00
ihor_ck
0d9dd6a33e Translated using Weblate (Ukrainian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-16 06:41:35 +02:00
WaldiS
e4137a031e Translated using Weblate (Polish)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-05-16 06:41:35 +02:00
Anonymous
41e9ea7e4f Translated using Weblate (Japanese)
Currently translated at 44.4% (192 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-05-16 06:41:35 +02:00
librada
1a8b88973d Translated using Weblate (Japanese)
Currently translated at 44.4% (192 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-05-16 06:41:35 +02:00
zykure
3109e251b9 Translated using Weblate (German)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-05-16 06:41:34 +02:00
zeritti
452ab280f1 Translated using Weblate (Czech)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-05-16 06:41:33 +02:00
solokot
34ecfbb846 Translated using Weblate (Russian)
Currently translated at 99.7% (431 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-05-14 10:01:38 +02:00
Andrew
11fb97d275 Translated using Weblate (Russian)
Currently translated at 99.7% (431 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-05-14 10:01:38 +02:00
Hosted Weblate
4bb30d075b Merge branch 'origin/master' into Weblate. 2020-05-12 09:06:25 +02:00
Oğuz Ersen
f38af55c05 Translated using Weblate (Turkish)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-05-11 22:01:00 +02:00
Allan Nordhøy
8a007376cc Translated using Weblate (Norwegian Bokmål)
Currently translated at 78.0% (337 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-05-11 22:01:00 +02:00
J-Jamet
940a1dfc78 Upgrade to versionCode 34 2020-05-10 20:07:58 +02:00
J-Jamet
cbd64d1602 Merge tag '2.5' into develop
2.5
2020-05-10 19:32:48 +02:00
J-Jamet
955c9992d0 Merge branch 'release/2.5' 2020-05-10 19:32:37 +02:00
J-Jamet
87606818cd Rename ReadMe.md to README.md 2020-05-10 16:18:32 +02:00
J-Jamet
ffcd264bac Upgrade manually Readme with pull request #540 2020-05-10 16:17:41 +02:00
J-Jamet
04c70cf99d Update fastlane full description (En/Fr - Free/Pro) 2020-05-10 15:31:07 +02:00
J-Jamet
0e0b6fca07 Fix small issue 2020-05-10 11:48:56 +02:00
J-Jamet
b24a789352 Fix small string 2020-05-10 11:46:35 +02:00
J-Jamet
3d4f447037 Replace <strong> tags 2020-05-10 11:40:15 +02:00
J-Jamet
7aa52ca00a Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-05-10 11:27:58 +02:00
J-Jamet
a94c9050c5 Update CHANGELOG and full description 2020-05-10 11:05:23 +02:00
J-Jamet
9b18e71e12 Upgrade CHANGELOGS 2020-05-10 10:14:37 +02:00
J-Jamet
d5ff048bd0 Remove allow_copy_password_key from application preferences 2020-05-10 10:12:18 +02:00
Allan Nordhøy
76f44f2573 Translated using Weblate (Romanian)
Currently translated at 91.6% (396 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2020-05-10 02:13:06 +02:00
Allan Nordhøy
9988f1a76f Translated using Weblate (Norwegian Bokmål)
Currently translated at 77.7% (336 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-05-10 02:13:05 +02:00
Xiang Xu
1559376bc2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-05-10 02:13:05 +02:00
ihor_ck
4d0f252acd Translated using Weblate (Ukrainian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-10 02:13:05 +02:00
Allan Nordhøy
d5a84e66ad Translated using Weblate (Swedish)
Currently translated at 76.8% (332 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-05-10 02:12:59 +02:00
Retrial
1dfe3453f4 Translated using Weblate (Greek)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-05-10 02:12:59 +02:00
ihor_ck
a7dc9f31b9 Translated using Weblate (Ukrainian)
Currently translated at 53.7% (232 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-07 09:36:51 +02:00
76c72cfe
d275f18b3c Translated using Weblate (Swedish)
Currently translated at 76.8% (332 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-05-07 09:36:48 +02:00
WaldiS
d770429401 Translated using Weblate (Polish)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-05-07 09:36:47 +02:00
Retrial
501f0be99f Translated using Weblate (Greek)
Currently translated at 88.1% (381 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-05-07 09:36:47 +02:00
J-Jamet
a70122f873 Fix small error 2020-05-06 18:19:07 +02:00
J-Jamet
1e1bd15a06 Replace vararg for non-compatible devices 2020-05-06 18:14:41 +02:00
J-Jamet
164fb1f4f5 Fix small warning 2020-05-06 17:45:56 +02:00
J-Jamet
0206514bdb Rollback stylish super implementation 2020-05-06 17:33:12 +02:00
J-Jamet
ecf2b42fa4 Fix field reference 2020-05-06 16:47:47 +02:00
ihor_ck
df683aeea9 Translated using Weblate (Ukrainian)
Currently translated at 32.6% (141 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-05 21:12:01 +02:00
Anonymous
cdd05958f7 Translated using Weblate (Ukrainian)
Currently translated at 32.6% (141 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-05 21:11:59 +02:00
solokot
4ec5fc05fb Translated using Weblate (Russian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-05-05 21:11:59 +02:00
Lucas Nunes
4019cafac1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2020-05-05 21:11:58 +02:00
J. Lavoie
4333e179d1 Translated using Weblate (Italian)
Currently translated at 95.3% (412 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-05-05 21:11:56 +02:00
Balázs Meskó
87d67be428 Translated using Weblate (Hungarian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2020-05-05 21:11:55 +02:00
J. Lavoie
350863adff Translated using Weblate (French)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-05-05 21:11:55 +02:00
J. Lavoie
d125fc46a0 Translated using Weblate (German)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-05-05 21:11:55 +02:00
jan madsen
5f4c079071 Translated using Weblate (Danish)
Currently translated at 98.1% (424 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-05-05 21:11:55 +02:00
zeritti
5ec576a76e Translated using Weblate (Czech)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-05-05 21:11:54 +02:00
Kunzisoft
9edb00ebbb Translated using Weblate (French)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-05-04 20:29:35 +02:00
ihor_ck
59843e5464 Translated using Weblate (Ukrainian)
Currently translated at 15.0% (65 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-05-04 02:22:13 +02:00
solokot
4fe0ccf807 Translated using Weblate (Russian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-05-02 20:32:07 +02:00
Anonymous
2ee012d3fe Translated using Weblate (Hungarian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2020-05-02 16:11:02 +02:00
Balázs Meskó
161b4cb5f5 Translated using Weblate (Hungarian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2020-05-02 16:11:02 +02:00
Anonymous
f9bee68d71 Translated using Weblate (Hungarian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2020-05-02 16:10:34 +02:00
Balázs Meskó
e0d98aca1b Translated using Weblate (Hungarian)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2020-05-02 16:08:07 +02:00
sshshsjsjaja
d47518c00c Translated using Weblate (German)
Currently translated at 100.0% (432 of 432 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-05-02 15:55:55 +02:00
J-Jamet
58f97dc3ab Upgrade to versionCode 33 2020-05-01 13:48:54 +02:00
J-Jamet
c7706cb80c Merge tag '2.5RC2' into develop
2.5RC2
2020-05-01 13:29:00 +02:00
J-Jamet
49bcc877ef Merge branch 'release/2.5RC2' 2020-05-01 13:28:50 +02:00
J-Jamet
4b81dd552e Update screens 2020-05-01 13:11:31 +02:00
J-Jamet
ff63aaf3f3 Remove unused file types 2020-04-30 21:14:15 +02:00
J-Jamet
39cbd1477b Upgrade CHANGELOG 2020-04-30 18:19:37 +02:00
J-Jamet
3741cc558a Remove field reference URL 2020-04-30 18:02:12 +02:00
J-Jamet
2a314ca3c1 Merge branch 'translations' into develop 2020-04-30 17:51:50 +02:00
J-Jamet
10f9564825 Remove unused comment 2020-04-30 17:51:23 +02:00
J-Jamet
1ae87c9b18 Fix small string issues 2020-04-30 17:50:10 +02:00
J-Jamet
24301ba462 Fix string strong tag 2020-04-30 17:43:19 +02:00
J-Jamet
ff6481274f Fix small string bug 2020-04-30 17:36:43 +02:00
J-Jamet
866df585a2 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-04-30 17:24:48 +02:00
J-Jamet
416bec04a1 Fix context 2020-04-30 17:07:52 +02:00
J-Jamet
f747d6725e Add flavor dimension 2020-04-30 17:03:43 +02:00
J-Jamet
8846918e55 Fix file manager dialog 2020-04-30 16:30:52 +02:00
J-Jamet
e347aefcd9 Manual merge of strings #460 2020-04-30 16:04:41 +02:00
J-Jamet
0a0676af3a Android backup management 2020-04-30 15:45:30 +02:00
Mik
7a69b63b4f Translated using Weblate (Italian)
Currently translated at 97.0% (421 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-30 03:32:39 +02:00
J-Jamet
fab5626741 Fix error message in password generator 2020-04-29 17:46:33 +02:00
J-Jamet
c6832d6478 Check permission in onResume 2020-04-29 16:56:47 +02:00
J-Jamet
f451597746 Add thread sleep 2020-04-29 16:35:17 +02:00
J-Jamet
a92e5b5156 Remove dialog task listener (issue dialog still in view) 2020-04-29 15:16:58 +02:00
J-Jamet
786b3b26ea Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2020-04-28 21:42:40 +02:00
J-Jamet
2e9fd2fd79 Merge branch 'feature/fix_opening' into develop 2020-04-28 21:42:18 +02:00
J-Jamet
3be2b9893b Move onActionFinish?.invoke(actionTask, result) 2020-04-26 20:00:47 +02:00
J-Jamet
7f6bed4f5f Replace EditText by TextInputEditText in InputLayout 2020-04-26 10:51:16 +02:00
J-Jamet
37322f284a Merge branch 'develop' into feature/fix_opening 2020-04-26 10:17:46 +02:00
J-Jamet
4106bb1792 Fix lock loop 2020-04-26 10:16:56 +02:00
J-Jamet
229db1242a Listener for task dialog 2020-04-26 09:10:18 +02:00
J-Jamet
8201b42135 Fix progress task dialog 2020-04-25 15:41:33 +02:00
J-Jamet
24818575dc Finish database task only if a binder is attach (to fix too fast opening) 2020-04-25 14:19:50 +02:00
J-Jamet
213050e7f2 Better task listener implementation 2020-04-24 22:09:46 +02:00
J-Jamet
1efaf4e3ea Kotlinized VariantDictionnary and fix database creation 2020-04-24 20:35:36 +02:00
J-Jamet
5532147992 Update CHANGELOG 2020-04-24 18:32:11 +02:00
J-Jamet
550b43094d Fix default username in existing entry #535 2020-04-24 18:30:17 +02:00
J-Jamet
5e831837c8 Update gradle kotlin 2020-04-24 18:20:07 +02:00
J-Jamet
480e88a088 Update gradle 2020-04-24 18:19:35 +02:00
J-Jamet
81cbcbe8af Upgrade TapTargetView lib to 1.13.0 2020-04-24 18:15:17 +02:00
J-Jamet
5649634809 Fix toolbar icon color after theme change 2020-04-24 17:55:44 +02:00
J-Jamet
69a253f738 Remove unused preference theme 2020-04-23 22:39:04 +02:00
J-Jamet
fbc8cfddb8 Fix purple style 2020-04-23 21:59:32 +02:00
J-Jamet
3e1024804c Try to fix preference icons 2020-04-23 21:37:24 +02:00
J-Jamet
cf0b51be00 Fix style purple 2020-04-23 17:53:44 +02:00
J-Jamet
5b295a2a8f Fix pref icon color for KitKat 2020-04-23 15:23:23 +02:00
Andrew
ebb3d7a149 Translated using Weblate (Russian)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-04-23 15:11:22 +02:00
J-Jamet
73ab348d11 Fix keyfile name view 2020-04-23 14:59:21 +02:00
J-Jamet
8ce17086f8 Fix show lock button in setting orientation change 2020-04-23 14:51:32 +02:00
J-Jamet
45cffc93b1 Try to fix theme issue #509 2020-04-23 14:32:59 +02:00
J-Jamet
71cf4d5a34 Fix requireContext, requireArguments and import 2020-04-23 13:43:57 +02:00
J-Jamet
b79a4154af Fix deprecated FragmentManager 2020-04-23 13:35:42 +02:00
J-Jamet
ff818f8472 Upgrade Preference lib 2020-04-23 12:37:12 +02:00
Kunzisoft
b540d32ca3 Added translation using Weblate (Bulgarian) 2020-04-23 09:45:08 +02:00
J-Jamet
895a11d45f Fix opening database wait 2020-04-22 21:07:56 +02:00
J-Jamet
bdafae6132 Fix small warning 2020-04-22 20:50:59 +02:00
J-Jamet
f45d9ce6da Merge branch 'feature/UnsignedValues' into develop 2020-04-22 20:33:35 +02:00
J-Jamet
7456e2c8f5 Refactor key encryption methods and add unit tests 2020-04-22 20:23:23 +02:00
J-Jamet
fc51b50fe6 Fix download_progression string 2020-04-22 18:57:55 +02:00
J-Jamet
b31aa26975 Update CHANGELOG 2020-04-22 18:51:32 +02:00
J-Jamet
49801e1b14 Merge branch 'feature/KeeWeb_Database' into develop 2020-04-22 18:49:50 +02:00
J-Jamet
1453464cbd Remove unused code 2020-04-22 17:46:00 +02:00
J-Jamet
b9be8ff13d Fix KDBX header reader for KeeWeb database #533 2020-04-22 17:30:20 +02:00
J-Jamet
9663c3cadd Add UnsignedInt and UnsignedLong 2020-04-22 15:55:21 +02:00
J-Jamet
da5490bc0a Remove unused import 2020-04-22 15:37:40 +02:00
anonymous
df9b1b9fbb Translated using Weblate (Russian)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-04-22 14:33:57 +02:00
zeritti
6a0a3ded13 Translated using Weblate (Czech)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-04-21 19:11:21 +02:00
J-Jamet
ac7f4a08c3 Replace empty recycle bin icon 2020-04-20 19:12:16 +02:00
J-Jamet
e5ff5e3364 Update CHANGELOG 2020-04-20 19:09:24 +02:00
J-Jamet
f0ac19fcc1 Fix "Go" button #523 2020-04-20 19:07:09 +02:00
J-Jamet
ce23d34923 Fix #530 2020-04-20 18:41:57 +02:00
WaldiS
bdbba87f5a Translated using Weblate (Polish)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-04-20 04:11:21 +02:00
Filippo De Bortoli
fa8ef0477b Translated using Weblate (Italian)
Currently translated at 89.1% (387 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-20 04:11:21 +02:00
Jeannette L
3e9ec4cfb6 Translated using Weblate (Italian)
Currently translated at 89.1% (387 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-04-20 04:11:20 +02:00
J-Jamet
ad1c9d8129 Merge branch 'feature/SearchParameters' into develop 2020-04-18 13:09:39 +02:00
J-Jamet
7aaac4c13c Encapsulate search and UUID hex string 2020-04-18 13:09:17 +02:00
J-Jamet
a6c71c3d54 StringUtil refactoring 2020-04-18 11:29:51 +02:00
J-Jamet
05714f38dc Add search in url for search info 2020-04-18 11:23:20 +02:00
J-Jamet
4ef5e4d8ae Refactor search parameters and Autofill search 2020-04-17 16:15:55 +02:00
Oğuz Ersen
5fd0e1d6fe Translated using Weblate (Turkish)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-16 13:11:25 +02:00
Allan Nordhøy
97faf5bef3 Translated using Weblate (Norwegian Bokmål)
Currently translated at 79.0% (343 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-04-16 13:11:25 +02:00
Destiny Li
bd77128733 Translated using Weblate (English)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-04-16 13:11:24 +02:00
Destiny Li
935debc6bf Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-04-16 13:11:24 +02:00
solokot
699ed19b6a Translated using Weblate (Russian)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-04-16 13:11:24 +02:00
Jeannette L
126e5a94c2 Translated using Weblate (French)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-04-16 13:11:23 +02:00
sshshsjsjaja
792bd36ea7 Translated using Weblate (German)
Currently translated at 100.0% (434 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-04-16 13:11:23 +02:00
jan madsen
04dfee1b43 Translated using Weblate (Danish)
Currently translated at 99.0% (430 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-04-16 13:11:23 +02:00
Carles Sadurní
6a08cfeea9 Translated using Weblate (Catalan)
Currently translated at 61.0% (265 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-16 13:11:22 +02:00
J-Jamet
29a0f6c9f6 Small amelioration 2020-04-15 20:06:14 +02:00
J-Jamet
e2bdf26d82 Fix autofill crash #520 2020-04-15 14:44:13 +02:00
Kunzisoft
1643949110 Translated using Weblate (French)
Currently translated at 99.7% (433 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-04-15 12:50:29 +02:00
J-Jamet
577dabd727 Better HashedBlockOutputStream implementation 2020-04-15 12:11:45 +02:00
J-Jamet
ed4bd0693f Fix small warning 2020-04-15 12:02:51 +02:00
J-Jamet
0f2982b34d Try to fix bad icon exception 2020-04-15 11:52:22 +02:00
C. Rüdinger
0954094800 Translated using Weblate (German)
Currently translated at 98.6% (428 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-04-14 23:35:52 +02:00
sshshsjsjaja
22dcfafc62 Translated using Weblate (German)
Currently translated at 98.6% (428 of 434 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-04-14 23:35:51 +02:00
J-Jamet
dd34051426 Update CHANGELOG 2020-04-14 20:28:25 +02:00
J-Jamet
6feaf2cb8a Update Autofill compatibility 2020-04-14 20:27:08 +02:00
J-Jamet
603f64ea92 Check TODOs 2020-04-14 20:23:50 +02:00
J-Jamet
568f9ab0d0 Check TODOs 2020-04-14 19:54:02 +02:00
J-Jamet
a2ff1c33e6 Update CHANGELOG 2020-04-14 19:33:29 +02:00
J-Jamet
84452e9fc0 Spongy Castle by Bouncy Castle replacement 2020-04-13 13:39:45 +02:00
J-Jamet
5a6ae453cf Upgrade strings comments 2020-04-13 10:03:49 +02:00
Hosted Weblate
cde3e3361d Merge branch 'origin/master' into Weblate. 2020-04-13 08:58:48 +02:00
Oğuz Ersen
25f01192a4 Translated using Weblate (Turkish)
Currently translated at 100.0% (429 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-04-13 08:58:47 +02:00
Carles Sadurní
bacb08ec65 Translated using Weblate (Catalan)
Currently translated at 16.7% (72 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-13 08:58:47 +02:00
anonymous
36c905ee1c Translated using Weblate (Catalan)
Currently translated at 18.1% (78 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-12 22:44:08 +02:00
Carles Sadurní
6b9280534a Translated using Weblate (Catalan)
Currently translated at 18.1% (78 of 429 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2020-04-12 22:44:08 +02:00
J-Jamet
f94c6e850f Add History of each entry in description 2020-04-12 21:20:54 +02:00
J-Jamet
f4cecf6906 Upgrade to version 2.5 2020-04-12 20:51:02 +02:00
J-Jamet
a81d3766f8 Merge tag '2.5RC1' into develop
2.5RC1
2020-04-12 20:32:12 +02:00
284 changed files with 6238 additions and 3416 deletions

View File

@@ -1,4 +1,34 @@
KeePassDX(2.5beta31)
KeePassDX(2.7)
* Add blocklists for autofill
* Add autofill compatibility mode (usefull for Browser not compatible)
* Upgrade autofill recognition algorithm
* Setting to search through web subdomains
* Refactoring selection mode
KeePassDX(2.6)
* Share a web domain to automatically search for an entry
* Default group icon for a new entry
* Better autofill recognition
* Fix entry not visually deleted in search
* Fix hanged loading dialog
* Fix auto open biometric prompt if comes from background
* Minor fixes
KeePassDX(2.5)
* First stable version of KeePassDX
* Fork completely rewritten from the KeePassDroid project
* Fix small issues from the last Release Candidate
KeePassDX(2.5RC2)
* Replacement of Spongy Castle by Bouncy Castle
* Update Autofill compatibility
* Fix Magikeyboard "Go" action
* Fix KeeWeb database opening
* Fix default username
* Fix themes
* Fix small issues
KeePassDX(2.5RC1)
* Add write permission to keep compatibility with old file managers
* Fix autofill for apps
* Auto search for autofill

View File

@@ -0,0 +1,177 @@
Terms of Service
This is the terms of service for the Android Backup Service.
1. Your relationship with Google
1.1 Your use of the Android Backup Service (referred to as the "Service" in this document) is subject to the terms of a legal agreement between you and Google. "Google" means Google LLC, whose principal place of business is at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. This document explains how the agreement is made up, and sets out some of the terms of that agreement.
1.2 Unless otherwise agreed in writing with Google, your agreement with Google will always include, at a minimum, the terms and conditions set out in this document. These are referred to below as the "Terms".
1.3 The Terms form a legally binding agreement between you and Google in relation to your use of the Service. It is important that you take the time to read them carefully.
2. Accepting the Terms
2.1 In order to use the Service, you must first agree to the Terms. You may not use the Service if you do not accept the Terms.
2.2 You can accept the Terms by clicking to accept or agree to the Terms, where this option is made available to you by Google.
2.3 You may not use the Service and may not accept the Terms if you are not of legal age to form a binding contract with Google.
2.4 You represent that you have full power, capacity and authority to accept these Terms. If you are accepting on behalf of your employer or another entity, you represent that you have full legal authority to bind your employer or such entity to these Terms. If you don't have the legal authority to bind, please ensure that an authorized person from your entity consents to and accepts these Terms.
3. Provision of the Service by Google
3.1 Google has subsidiaries and affiliated legal entities around the world ("Subsidiaries and Affiliates"). Sometimes, these companies will be providing the Service to you on behalf of Google itself. You acknowledge and agree that Subsidiaries and Affiliates will be entitled to provide the Service to you.
3.2 Google is constantly innovating in order to provide the best possible experience for its users. You acknowledge and agree that the form and nature of the Service which Google provides may change from time to time without prior notice to you.
3.3 As part of this continuing innovation, you acknowledge and agree that Google may stop (permanently or temporarily) providing the Service (or any features within the Service) to you or to users generally at Google's sole discretion, without prior notice to you. You may stop using the Service at any time. You do not need to specifically inform Google when you stop using the Service.
3.4 You acknowledge and agree that if Google disables your Backup Service Key, you and the Android application(s) you developed ("Application(s)") may be prevented from accessing the Service and any content that is stored with the Service.
3.5 You acknowledge and agree that Google may set a fixed upper limit on the number of backup transmissions you may send or receive through the Service or on the amount of storage space used for the provision of the Service at any time, at Google's discretion. You agree to abide by any such fixed upper limits.
4. Use of the Service by you
4.1 In order to access the Service, you must have a unique application identifier ("Package Name") for your Application as described in the documentation for the Service.
4.2 After supplying Google with the Package Name and accepting the Terms, you will be issued an alphanumeric key ("Backup Service Key") assigned to you by Google that is uniquely associated with your Application. Your Application must include this Backup Service Key as described in the documentation for the Service.
4.3 There is currently no limit to the number of Backup Service Keys you may obtain in this manner provided that you use a different Package Name for each Backup Service Key you obtain. You agree that each Backup Service Key is only valid for Applications with the corresponding Package Name. You agree that Google may, in its sole discretion, impose a limit on the number of Backup Service Keys that may be obtained in the future. You agree that your continued use of any of the Backup Service Keys assigned by Google, or distribution of any Applications using such Backup Service Keys, constitutes your continued agreement to these Terms.
4.4 You agree to use the Service only for purposes that are permitted by (a) the Terms and (b) any applicable law, regulation, third-party terms of service, or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
4.5 You agree not to access (or attempt to access) any of the Service by any means other than through the interfaces, methods, and APIs that are provided by Google, unless you have been specifically allowed to do so in a separate agreement with Google.
4.6 You agree that you will not engage in any activity that interferes with or disrupts the Service (or the servers and networks which are connected to the Service), or the servers or networks of any third-party.
4.7 You agree that your use of the Service will be in compliance with any documentation guidelines provided by Google and that failure to comply with the documentation guidelines may result in the disabling of the Backup Service Key(s) for your Application(s).
4.8 Unless you have been specifically permitted to do so in a separate agreement with Google, you agree that you will not reproduce, duplicate, copy, sell, trade or resell (a) use of the Service, or (b) access to the Service.
4.9 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) your and your Application's use of the Service, any breach of your obligations under the Terms, and for the consequences (including any loss or damage which Google may suffer) of any such breach.
4.10 You agree that in your use of the Service, you and your Applications will protect the privacy and legal rights of users. You must provide legally adequate privacy notice and protection for users whose data your Applications back up to the Service. Further, your Application may only use that information for the limited purpose of backing up the data to the Service unless the user has given you permission for further use. If the user has not given you permission to back up information to the Service, you may not transmit such information to the Service.
4.11 You agree that you and your Applications will not transmit or store sensitive user information, such as user names, passwords, or credit card numbers, through the Service.
5. Security
5.1 You agree and understand that you are responsible for maintaining the security associated with any information you provide to access the Service as well as of the Backup Service Key(s) assigned to you by Google. You agree that only you are authorized to use the Backup Service Key(s) assigned to you.
5.2 Accordingly, you agree that you will be solely responsible to Google for all activities that occur in connection with your access to the Service, as well as the Backup Service Key.
5.3 If you become aware of any unauthorized use of your Backup Service Key(s) you agree to notify Google immediately.
6. Privacy and your personal information
6.1 For information about Google's data protection practices, please read Google's privacy policy at http://www.google.com/privacy.html. This policy explains how Google treats your personal information when you use the Service.
6.2 You agree to the use of your data in accordance with Google's privacy policies.
7. Content in the Service
7.1 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any Content that you or your Applications transmit or store through the Service and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. You agree that you are solely responsible for (A) any Content that is transmitted through the Service by your Applications, and (B) any Content that Devices retrieve from the Service by virtue of your Applications. For purposes of the Terms, "Content" means information such as data, messages, settings information, written text, computer software, music, audio files or other sounds, photographs, videos or other images. "Device(s)" means device(s) powered by the Android operating system.
7.2 You agree that you will not transmit any Content through the Service that is copyrighted, protected by trade secret or otherwise subject to third party proprietary rights, including patent, privacy and publicity rights, unless you are the owner of such rights or have permission from their rightful owner to transmit the Content through the Service.
8. Proprietary rights
8.1 You acknowledge and agree that Google (or Google's licensors) own all legal right, title and interest in and to the Service, including any intellectual property rights which subsist in the Service (whether those rights happen to be registered or not, and wherever in the world those rights may exist).
8.2 Unless you have agreed otherwise in writing with Google, nothing in the Terms gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, and other distinctive brand features.
8.3 If you have been given an explicit right to use any of these brand features in a separate written agreement with Google, then you agree that your use of such features shall be in compliance with that agreement, any applicable provisions of the Terms, and Google's brand feature use guidelines as updated from time to time. These guidelines can be viewed online at http://www.google.com/permissions/ guidelines.html (or such other URL as Google may provide for this purpose from time to time).
8.4 You agree that you shall not remove, obscure, or alter any proprietary rights notices (including copyright, trade mark notices) which may be affixed to or contained within the Service.
9. License from Google
9.1 Subject to terms and conditions of these Terms, Google gives you a personal, worldwide, royalty-free, non-assignable and non-exclusive license to use the Service as provided to you by Google. This license is for the sole purpose of enabling you to use and enjoy the benefit of the Service as provided by Google, in the manner permitted by the Terms.
9.2 You may not (and you may not permit anyone else to) copy, modify, create a derivative work of, reverse engineer, decompile or otherwise attempt to extract the source code from the Service or any part thereof, unless this is expressly permitted or required by law, or unless you have been specifically told that you may do so by Google, in writing.
9.3 Unless Google has given you specific written permission to do so, you may not assign (or grant a sub-license of) your rights to use the Service, grant a security interest in or over your rights to use the Service, or otherwise transfer any part of your rights to use the Service.
10. Your code
10.1 Google claims no ownership or control over any source code written by you to be used with the Service. You retain copyright and any other rights you already hold in this code, and you are responsible for protecting those rights, as appropriate.
11. Ending your relationship with Google
11.1 The Terms will continue to apply until terminated by either you or Google as set out below.
11.2 You may terminate your legal agreement with Google by discontinuing your use of the Service at any time.
11.3 Google may, at any time, terminate its legal agreement with you if:
(A) you have breached any provision of the Terms (or have acted in manner which clearly shows that you do not intend to, or are unable to comply with the provisions of the Terms); or
(B) Google is required to do so by law (for example, where the provision of the Service to you is, or becomes, unlawful); or
(C) Google is transitioning to no longer providing the Service; or
(D) your Application fails to meet the documentation guidelines provided by Google.
11.4 Nothing in this Section shall affect Google's rights regarding provision of the Service under Section 3 of the Terms.
11.5 When these Terms come to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the Terms have been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of Sections 12, 13 and Paragraph 16 shall continue to apply to such rights, obligations and liabilities indefinitely.
12. EXCLUSION OF WARRANTIES
12.1 NOTHING IN THESE TERMS, INCLUDING SECTIONS 12 AND 13, SHALL EXCLUDE OR LIMIT GOOGLE'S WARRANTY OR LIABILITY FOR LOSSES WHICH MAY NOT BE LAWFULLY EXCLUDED OR LIMITED BY APPLICABLE LAW. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR CONDITIONS OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR LOSS OR DAMAGE CAUSED BY NEGLIGENCE, BREACH OF CONTRACT OR BREACH OF IMPLIED TERMS, OR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, ONLY THE LIMITATIONS WHICH ARE LAWFUL IN YOUR JURISDICTION WILL APPLY TO YOU AND OUR LIABILITY WILL BE LIMITED TO THE MAXIMUM EXTENT PERMITTED BY LAW.
12.2 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SERVICE IS AT YOUR SOLE RISK AND THAT THE SERVICE AND CONTENT ARE PROVIDED "AS IS" AND "AS AVAILABLE".
12.3 IN PARTICULAR, GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS DO NOT REPRESENT OR WARRANT TO YOU THAT:
(A) YOUR USE OF THE SERVICE WILL MEET YOUR REQUIREMENTS,
(B) YOUR USE OF THE SERVICE WILL BE UNINTERRUPTED, TIMELY, SECURE OR FREE FROM ERROR, AND
(C) THAT DEFECTS IN THE OPERATION OR FUNCTIONALITY OF ANY SOFTWARE PROVIDED TO YOU AS PART OF THE SERVICE WILL BE CORRECTED.
12.4 NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM GOOGLE OR THROUGH OR FROM THE SERVICE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THE TERMS.
12.5 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
13. LIMITATION OF LIABILITY
13.1 SUBJECT TO OVERALL PROVISION IN PARAGRAPH 12.1 ABOVE, YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU FOR:
(A) ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL CONSEQUENTIAL OR EXEMPLARY DAMAGES WHICH MAY BE INCURRED BY YOU, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY. THIS SHALL INCLUDE, BUT NOT BE LIMITED TO, ANY LOSS OF PROFIT (WHETHER INCURRED DIRECTLY OR INDIRECTLY), ANY LOSS OF GOODWILL OR BUSINESS REPUTATION, ANY LOSS OF DATA SUFFERED, COST OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICE, OR OTHER INTANGIBLE LOSS;
(B) ANY LOSS OR DAMAGE WHICH MAY BE INCURRED BY YOU, INCLUDING BUT NOT LIMITED TO LOSS OR DAMAGE AS A RESULT OF:
(I) ANY CHANGES WHICH GOOGLE MAY MAKE TO THE SERVICE, OR FOR ANY PERMANENT OR TEMPORARY CESSATION IN THE PROVISION OF THE SERVICE (OR ANY FEATURES WITHIN THE SERVICE);
(II) THE DELETION OF, CORRUPTION OF, OR FAILURE TO STORE, ANY CONTENT AND OTHER COMMUNICATIONS DATA MAINTAINED OR TRANSMITTED BY OR THROUGH YOUR USE OF THE SERVICE;
(III) YOUR FAILURE TO PROVIDE GOOGLE WITH ACCURATE ACCOUNT INFORMATION; OR
(IV) YOUR FAILURE TO KEEP YOUR PASSWORD OR ACCOUNT DETAILS SECURE AND CONFIDENTIAL.
13.2 THE LIMITATIONS ON GOOGLE'S LIABILITY TO YOU IN PARAGRAPH 13.1 ABOVE SHALL APPLY WHETHER OR NOT GOOGLE HAS BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
14. Indemnification
14.1 You agree to hold harmless and indemnify Google, and its subsidiaries, affiliates, officers, agents, employees, or licensors from and against any third party claim arising from or in any way related to (a) your breach of the Terms, (b) your use of the Service, or (c) your violation of applicable laws, rules or regulations in connection with the Service, including any liability or expense arising from all claims, losses, damages (actual and consequential), suits, judgments, litigation costs and attorneys' fees, of every kind and nature. In such a case, Google will provide you with written notice of such claim, suit or action.
15. Changes to the Terms
15.1 Due to things like changes to the law or changes to functionality offered through the Service, Google may need to change these Terms from time to time. You should look at the Terms regularly. We'll post notice of the modified Terms within, or through, the Service. Once the modified Terms are posted, the changes will become effective immediately, and you are deemed to have accepted the modified Terms if you continue to use the Service. If you do not agree to the modified Terms for the Service, please stop using the Service.
16. General legal terms
16.1 The Terms constitute the whole legal agreement between you and Google and govern your use of the Service (but excluding any service which Google may provide to you under a separate written agreement), and completely replace any prior agreements between you and Google in relation to the Service.
16.2 You agree that Google may provide you with notices, including those regarding changes to the Terms, by email, regular mail, or postings on the Service.
16.3 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the Terms (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
16.4 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of these Terms is invalid, then that provision will be removed from the Terms without affecting the rest of the Terms. The remaining provisions of the Terms will continue to be valid and enforceable.
16.5 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the Terms and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the Terms which confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the Terms.
16.6 The Terms, and your relationship with Google under the Terms, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the Terms. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.

91
README.md Normal file
View File

@@ -0,0 +1,91 @@
# Android KeepassDX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeepassDX is a **multi-format KeePass manager for Android devices**. The app allows creating keys and passwords in a secure way by integrating with the Android design standards.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
### Features
- Create database files / entries and groups.
- Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
- **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC, …).
- Allows opening and **copying URI / URL fields quickly**.
- **Biometric recognition** for fast unlocking *(fingerprint / face unlock / …)*.
- **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA).
- Material design with **themes**.
- **Auto-Fill** and Integration.
- Field filling **keyboard**.
- **History** of each entry.
- Precise management of **settings**.
- Code written in **native languages** *(Kotlin / Java / JNI / C)*.
KeepassDX is **open source** and **ad-free**.
## What is KeePassDX?
An alternative to remembering an endless list of passwords manually. This is made more difficult by **using different passwords for each account**. If you use one password everywhere and security fails only one of those places, it grants access to your e-mail account, website, etc, and you may not know about it or notice, before bad things happen.
KeePassDX is a **password manager for Android**, which helps you **manage your passwords in a secure way**. You can put all your passwords in one database, locked with a **master key** and/or a **keyfile**. You **only have to remember one single master password and/or select the keyfile** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
## Small print?
KeePassDX is under **open source GPL3 license**, meaning you can use, study, change and share it at will. Copyleft ensures it stays that way.
From the full source, anyone can build, fork, and check whether for example the encryption algorithms are implemented correctly.
There is **no advertising**.
Do not worry, **the main features remain completely free**.
Optional visual styles are accessible after a contribution (and a congratulatory message (Ո‿Ո) ) or the purchase of an extended version to encourage contribution to the work of open source projects!
*If you contribute to the project and do not have access to the styles, do not hesitate to contact the author at [contact@kunzisoft.com](contact@kunzisoft.com).*
## Contributions
* Add features by making a **[pull request](https://help.github.com/articles/about-pull-requests/)**.
* Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** KeePassDX to your language (on [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or by sending a [pull request](https://help.github.com/articles/about-pull-requests/)).
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePassDX.
## Download
*[F-Droid](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) is the recommended way of installing, a libre software project that verifies that all the libraries and app code is libre software.*
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
## Frequently Asked Questions
Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
## Other devices
- [KeePass](https://keepass.info/) (https://keepass.info/) is the original and official project for the desktop, with technical documentation for standardized database files. It is updated regularly with active maintenance (written in C#).
- [KeePassXC](https://keepassxc.org/) (https://keepassxc.org/) is an alternative integration of KeePass written in C++.
- [KeeWeb](https://keeweb.info/) (https://keeweb.info/) is a web version that is also compatible with KeePass files.
## License
Copyright © 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePassDX.
[KeePassDX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePassDX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*

View File

@@ -1,87 +0,0 @@
# Android KeepassDX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeepassDX is a **multi-format KeePass manager for Android devices**. The application allows to create keys and passwords in a secure way by integrating with the Android design standards.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
### Features
* Create database files / entries and groups
* Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
* Allows **fast copy** of fields and opening of URI / URL
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
* **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
* Material design with **themes**
* **AutoFill** and Integration
* Field filling **keyboard**
* Precise management of **settings**
* Code written in **native language** *(Kotlin / Java / JNI / C)*
KeepassDX is **open source** and **ad-free**.
## What is KeePassDX?
Today you need to remember many passwords. You need a password for your e-mail account, your website's FTP password, online passwords (like website member account), etc. etc. etc. The list is endless. Also, you **should use different passwords for each account**. Because if you use only one password everywhere and someone gets this password you have a problem... A serious problem. The thief would have access to your e-mail account, website, etc. Unimaginable.
KeePassDX is a **free open source password manager for Android**, which helps you to **manage your passwords in a secure way**. You can put all your passwords in one database, which is locked with one **master key** or a **key file**. So you **only have to remember one single master password or select the key file** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
## Is it really free?
Yes, KeePassDX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
*Note : If you access the application from a store, visual styles may not be available to encourage contribution to the work of open source projects. These optional styles are accessible after a contribution (and a 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
You can contribute in different ways to help us on our work.
* Add features by a **[pull request](https://help.github.com/articles/about-pull-requests/)**.
* Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** into your language (By using [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or with a manual [pull request](https://help.github.com/articles/about-pull-requests/))
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePassDX
## Download
*We recommend the installation from [F-Droid](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) which verifies that all libraries and application code are open source.*
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
## Frequently Asked Questions
Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
## Other devices
- [KeePass](https://keepass.info/) (https://keepass.info/) is the original and official project for desktop, with technical documentation for standardized database files. It is updated regularly with active maintenance (written in C#).
- [KeePassXC](https://keepassxc.org/) (https://keepassxc.org/) is an alternative integration to KeePass written in C++.
- [KeeWeb](https://keeweb.info/) (https://keeweb.info/) is a web version also compatible with KeePass files.
## License
Copyright (c) 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePassDX.
[KeePassDX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePassDX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*

View File

@@ -11,14 +11,15 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 29
versionCode = 31
versionName = "2.5RC1"
versionCode = 35
versionName = "2.7"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"" ]
kapt {
arguments {
@@ -41,35 +42,36 @@ android {
}
}
dexOptions {
}
flavorDimensions "tier"
flavorDimensions "version"
productFlavors {
libre {
dimension "version"
applicationIdSuffix = ".libre"
buildConfigField "String", "BUILD_VERSION", "\"libre\""
buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "false"
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
}
pro {
dimension "version"
applicationIdSuffix = ".pro"
buildConfigField "String", "BUILD_VERSION", "\"pro\""
buildConfigField "boolean", "FULL_VERSION", "true"
buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", "{}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIZiXvrQCzSV9LNI6-p7cjTKENZLHIrz_zaqZuQQ" ]
}
free {
dimension "version"
applicationIdSuffix = ".free"
buildConfigField "String", "BUILD_VERSION", "\"free\""
buildConfigField "boolean", "FULL_VERSION", "false"
buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
buildConfigField "String[]", "STYLES_DISABLED", "{}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIbRfbV8fHLItXo8OcHwrO0sSNblqhPwkc0DPTqg" ]
}
}
@@ -83,41 +85,44 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
def spongycastleVersion = "1.58.0.0"
def room_version = "2.2.5"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.0.1'
implementation "androidx.core:core-ktx:1.2.0"
implementation 'androidx.core:core-ktx:1.2.0'
// To upgrade with style
implementation 'com.google.android.material:material:1.0.0'
// Database
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Crypto
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
// Time
implementation 'joda-time:joda-time:2.9.9'
implementation 'joda-time:joda-time:2.10.6'
// Color
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
// Education
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.0'
// Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2'
implementation 'commons-collections:commons-collections:3.2.2'
// Apache Commons Codec
implementation 'commons-codec:commons-codec:1.11'
implementation 'commons-codec:commons-codec:1.14'
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')
// Tests
androidTestImplementation 'junit:junit:4.13'
}

View File

@@ -26,11 +26,11 @@ import java.util.Random
import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
class FinalKeyTest : TestCase() {
private var mRand: Random? = null
class AESKeyTest : TestCase() {
private lateinit var mRand: Random
@Throws(Exception::class)
override fun setUp() {
@@ -40,29 +40,28 @@ class FinalKeyTest : TestCase() {
}
@Throws(IOException::class)
fun testNativeAndroid() {
fun testAES() {
// Test both an old and an even number to test my flip variable
testNativeFinalKey(5)
testNativeFinalKey(6)
testAESFinalKey(5)
testAESFinalKey(6)
}
@Throws(IOException::class)
private fun testNativeFinalKey(rounds: Int) {
private fun testAESFinalKey(rounds: Long) {
val seed = ByteArray(32)
val key = ByteArray(32)
val nativeKey: ByteArray
val androidKey: ByteArray
val nativeKey: ByteArray?
val androidKey: ByteArray?
mRand!!.nextBytes(seed)
mRand!!.nextBytes(key)
mRand.nextBytes(seed)
mRand.nextBytes(key)
val aKey = AndroidFinalKey()
androidKey = aKey.transformMasterKey(seed, key, rounds.toLong())
val androidAESKey = AndroidAESKeyTransformer()
androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
val nKey = NativeFinalKey()
nativeKey = nKey.transformMasterKey(seed, key, rounds.toLong())
val nativeAESKey = NativeAESKeyTransformer()
nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
assertArrayEquals("Does not match", androidKey, nativeKey)
}
}

View File

@@ -80,6 +80,4 @@ class AESTest : TestCase() {
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
}
}

View File

@@ -33,7 +33,7 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
class HashedBlock : TestCase() {
class HashedBlockTest : TestCase() {
@Throws(IOException::class)
fun testBlockAligned() {

View File

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

View File

@@ -0,0 +1,28 @@
package com.kunzisoft.keepass.tests.utils
import com.kunzisoft.keepass.utils.UnsignedInt
import junit.framework.TestCase
class UnsignedIntTest: TestCase() {
fun testUInt() {
val standardInt = UnsignedInt(15).toInt()
assertEquals(15, standardInt)
val unsignedInt = UnsignedInt(-1).toLong()
assertEquals(4294967295L, unsignedInt)
}
fun testMaxValue() {
val maxValue = UnsignedInt.MAX_VALUE.toLong()
assertEquals(4294967295L, maxValue)
val longValue = UnsignedInt.fromLong(4294967295L).toLong()
assertEquals(longValue, maxValue)
}
fun testLong() {
val longValue = UnsignedInt.fromLong(50L).toInt()
assertEquals(50, longValue)
val uIntLongValue = UnsignedInt.fromLong(4294967290).toLong()
assertEquals(4294967290, uIntLongValue)
}
}

View File

@@ -17,17 +17,18 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests
package com.kunzisoft.keepass.tests.utils
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.*
class StringDatabaseKDBUtilsTest : TestCase() {
class ValuesTest : TestCase() {
fun testReadWriteLongZero() {
testReadWriteLong(0.toByte())
@@ -77,11 +78,10 @@ class StringDatabaseKDBUtilsTest : TestCase() {
setArray(orig, value, 4)
val one = bytes4ToInt(orig)
val dest = intTo4Bytes(one)
val one = bytes4ToUInt(orig)
val dest = uIntTo4Bytes(one)
assertArrayEquals(orig, dest)
}
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
@@ -133,7 +133,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
}
private fun testReadWriteByte(value: Byte) {
val dest: Byte = uIntToByte(byteToUInt(value))
val dest: Byte = UnsignedInt(UnsignedInt.fromByte(value)).toByte()
assert(value == dest)
}
@@ -185,7 +185,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
val bos = ByteArrayOutputStream()
val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(ULONG_MAX_VALUE)
leos.writeLong(UnsignedLong.MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()

View File

@@ -23,17 +23,15 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:name="com.kunzisoft.keepass.app.App"
android:allowBackup="true"
android:fullBackupContent="@xml/backup"
android:fullBackupContent="@xml/backup_rules"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true"
android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night"
tools:targetApi="n">
<!-- TODO backup API Key -->
<meta-data
android:name="com.google.android.backup.api_key"
android:value="" />
android:value="${googleAndroidBackupAPIKey}" />
<activity
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
android:theme="@style/KeepassDXStyle.SplashScreen"
@@ -133,16 +131,30 @@
android:name="com.kunzisoft.keepass.activities.AboutActivity"
android:launchMode="singleTask"
android:label="@string/about" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
<activity
android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity
android:name="com.kunzisoft.keepass.activities.AutofillLauncherActivity"
android:theme="@style/Theme.Transparent"
android:configChanges="keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
<activity android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
android:label="@string/keyboard_name"
android:exported="true">
<activity
android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
<activity
android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
<activity
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
<activity
android:name="com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity"
android:theme="@style/Theme.Transparent" />
<activity
android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
android:label="@string/keyboard_setting_label">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

View File

@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.autofill
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.app.PendingIntent
@@ -26,27 +26,45 @@ import android.content.Intent
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.KeeAutofillService
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
@RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Build search param
val searchInfo = SearchInfo().apply {
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
}
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val assistStructure = AutofillHelper.retrieveAssistStructure(intent)
if (assistStructure != null) {
// Build search param
val searchInfo = SearchInfo().apply {
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
}
if (assistStructure == null) {
setResult(Activity.RESULT_CANCELED)
finish()
} else if (!KeeAutofillService.searchAllowedFor(searchInfo.applicationId,
PreferencesUtil.applicationIdBlocklist(this))
|| !KeeAutofillService.searchAllowedFor(searchInfo.webDomain,
PreferencesUtil.webDomainBlocklist(this))) {
// If item not allowed, show a toast
Toast.makeText(this.applicationContext, R.string.autofill_block_restart, Toast.LENGTH_LONG).show()
setResult(Activity.RESULT_CANCELED)
finish()
} else {
// If database is open
AutofillHelper.checkAutoSearchInfo(this,
SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
@@ -57,17 +75,17 @@ class AutofillLauncherActivity : AppCompatActivity() {
{
// Show the database UI to select the entry
GroupActivity.launchForAutofillResult(this,
assistStructure)
assistStructure,
false,
searchInfo)
},
{
// If database not open
FileDatabaseSelectActivity.launchForAutofillResult(this,
assistStructure, searchInfo)
assistStructure,
searchInfo)
}
)
} else {
setResult(Activity.RESULT_CANCELED)
finish()
}
super.onCreate(savedInstanceState)

View File

@@ -371,7 +371,6 @@ class EntryActivity : LockingActivity() {
taColorAccent.recycle()
}
val entryHistory = entry.getHistory()
// TODO isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) {
@@ -485,9 +484,7 @@ class EntryActivity : LockingActivity() {
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
},
{
// Open Keepass doc to create field references
startActivity(Intent(Intent.ACTION_VIEW,
UriUtil.parse(getString(R.string.field_references_url))))
performedNextEducation(entryActivityEducation, menu)
})
}
}

View File

@@ -43,6 +43,7 @@ import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
@@ -166,9 +167,16 @@ class EntryEditActivity : LockingActivity(),
mNewEntry = mDatabase?.createEntry()
}
mParent = mDatabase?.getGroupById(it)
// Add the default icon
mDatabase?.drawFactory?.let { iconFactory ->
entryEditContentsView?.setDefaultIcon(iconFactory)
// Add the default icon from parent if not a folder
val parentIcon = mParent?.icon
if (parentIcon != null
&& parentIcon.iconId != IconImage.UNKNOWN_ID
&& parentIcon.iconId != IconImageStandard.FOLDER) {
temporarilySaveAndShowSelectedIcon(parentIcon)
} else {
mDatabase?.drawFactory?.let { iconFactory ->
entryEditContentsView?.setDefaultIcon(iconFactory)
}
}
}
@@ -268,7 +276,10 @@ class EntryEditActivity : LockingActivity(),
// Set info in view
entryEditContentsView?.apply {
title = newEntry.title
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
username = if (mIsNew && newEntry.username.isEmpty())
mDatabase?.defaultUsername ?: ""
else
newEntry.username
url = newEntry.url
password = newEntry.password
expires = newEntry.expires

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
/**
* Activity to search or select entry in database,
* Commonly used with Magikeyboard
*/
class EntrySelectionLauncherActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
var sharedWebDomain: String? = null
when (intent?.action) {
Intent.ACTION_SEND -> {
if ("text/plain" == intent.type) {
// Retrieve web domain
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
sharedWebDomain = Uri.parse(it).host
}
}
}
else -> {}
}
// Setting to integrate Magikeyboard
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
// Build search param
val searchInfo = SearchInfo().apply {
webDomain = sharedWebDomain
}
// If database is open
SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
// Items found
if (searchShareForMagikeyboard) {
if (items.size == 1) {
// Automatically populate keyboard
val entryPopulate = items[0]
populateKeyboardAndMoveAppToBackground(this,
entryPopulate,
intent)
} else {
// Select the one we want
GroupActivity.launchForEntrySelectionResult(this,
true,
searchInfo)
}
} else {
GroupActivity.launch(this,
true,
searchInfo)
}
},
{
// Show the database UI to select the entry
if (searchShareForMagikeyboard) {
GroupActivity.launchForEntrySelectionResult(this,
false,
searchInfo)
} else {
GroupActivity.launch(this,
false,
searchInfo)
}
},
{
// If database not open
if (searchShareForMagikeyboard) {
FileDatabaseSelectActivity.launchForEntrySelectionResult(this,
searchInfo)
} else {
FileDatabaseSelectActivity.launch(this,
searchInfo)
}
}
)
finish()
super.onCreate(savedInstanceState)
}
}
fun populateKeyboardAndMoveAppToBackground(activity: Activity,
entry: EntryInfo,
intent: Intent,
toast: Boolean = true) {
// Populate Magikeyboard with entry
MagikIME.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
// Consume the selection mode
EntrySelectionHelper.removeEntrySelectionModeFromIntent(intent)
activity.moveTaskToBack(true)
}

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.activities
import android.annotation.SuppressLint
import android.app.Activity
import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
@@ -41,12 +42,12 @@ import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
@@ -58,13 +59,14 @@ import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.*
import java.io.FileNotFoundException
class FileDatabaseSelectActivity : StylishActivity(),
class FileDatabaseSelectActivity : SpecialModeActivity(),
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
// Views
private var coordinatorLayout: CoordinatorLayout? = null
private var fileManagerExplanationButton: View? = null
private var createButtonView: View? = null
private var databaseButtonsContainerView: View? = null
private var createDatabaseButtonView: View? = null
private var openDatabaseButtonView: View? = null
// Adapter to manage database history list
@@ -95,19 +97,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
}
// Create button
createButtonView = findViewById(R.id.create_database_button)
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE
}
else{
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
databaseButtonsContainerView = findViewById(R.id.database_buttons_container)
createButtonView?.setOnClickListener { createNewFile() }
// Create database button
createDatabaseButtonView = findViewById(R.id.create_database_button)
createDatabaseButtonView?.setOnClickListener { createNewFile() }
// Open database button
mOpenFileHelper = OpenFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.apply {
@@ -156,7 +152,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
UriUtil.parse(databasePath)?.let { databaseFileUri ->
launchPasswordActivityWithPath(databaseFileUri)
} ?: run {
Log.i(TAG, "Unable to launch Password Activity")
Log.i(TAG, "No default database to prepare")
}
}
@@ -196,23 +192,30 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
EntrySelectionHelper.doEntrySelectionAction(intent,
{
try {
PasswordActivity.launch(this@FileDatabaseSelectActivity,
databaseUri, keyFile)
databaseUri, keyFile,
searchInfo)
} catch (e: FileNotFoundException) {
fileNoFoundAction(e)
}
// Remove the search info from intent
if (searchInfo != null) {
finish()
}
},
{
try {
PasswordActivity.launchForKeyboardResult(this@FileDatabaseSelectActivity,
databaseUri, keyFile)
finish()
databaseUri, keyFile,
searchInfo)
} catch (e: FileNotFoundException) {
fileNoFoundAction(e)
}
finish()
},
{ assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -220,23 +223,27 @@ class FileDatabaseSelectActivity : StylishActivity(),
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
databaseUri, keyFile,
assistStructure,
intent.getParcelableExtra(KEY_SEARCH_INFO))
searchInfo)
} catch (e: FileNotFoundException) {
fileNoFoundAction(e)
}
}
})
}
private fun launchGroupActivity(readOnly: Boolean) {
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
EntrySelectionHelper.doEntrySelectionAction(intent,
{
GroupActivity.launch(this@FileDatabaseSelectActivity,
false,
searchInfo,
readOnly)
},
{
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity,
GroupActivity.launchForEntrySelectionResult(this@FileDatabaseSelectActivity,
false,
searchInfo,
readOnly)
// Do not keep history
finish()
@@ -245,7 +252,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
assistStructure,
intent.getParcelableExtra(KEY_SEARCH_INFO),
false,
searchInfo,
readOnly)
}
})
@@ -259,37 +267,52 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
override fun onResume() {
super.onResume()
// Show open and create button or special mode
if (mSelectionMode) {
// Disable buttons if in selection mode or request for autofill
databaseButtonsContainerView?.visibility = View.GONE
} else {
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent.
createDatabaseButtonView?.visibility = View.VISIBLE
} else{
// No Activity found that can handle this intent.
createDatabaseButtonView?.visibility = View.GONE
}
databaseButtonsContainerView?.visibility = View.VISIBLE
}
val database = Database.getInstance()
if (database.loaded) {
launchGroupActivity(database.isReadOnly)
}
super.onResume()
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this)) {
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let { historyList ->
val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity)
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(
// Show only uri accessible
historyList.filter {
if (hideBrokenLocations) {
FileDatabaseInfo(this@FileDatabaseSelectActivity,
it.databaseUri).exists
} else
true
})
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this)) {
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let { historyList ->
val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity)
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(
// Show only uri accessible
historyList.filter {
if (hideBrokenLocations) {
FileDatabaseInfo(this@FileDatabaseSelectActivity,
it.databaseUri).exists
} else
true
})
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
// Register progress task
mProgressDialogThread?.registerProgressTask()
// Register progress task
mProgressDialogThread?.registerProgressTask()
}
}
override fun onPause() {
@@ -368,7 +391,10 @@ class FileDatabaseSelectActivity : StylishActivity(),
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
MenuUtil.defaultMenuInflater(menuInflater, menu)
if (!mSelectionMode) {
MenuUtil.defaultMenuInflater(menuInflater, menu)
}
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
@@ -377,11 +403,11 @@ class FileDatabaseSelectActivity : StylishActivity(),
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
// If no recent files
val createDatabaseEducationPerformed = createButtonView != null && createButtonView!!.visibility == View.VISIBLE
val createDatabaseEducationPerformed = createDatabaseButtonView != null && createDatabaseButtonView!!.visibility == View.VISIBLE
&& mAdapterDatabaseHistory != null
&& mAdapterDatabaseHistory!!.itemCount > 0
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createButtonView!!,
createDatabaseButtonView!!,
{
createNewFile()
},
@@ -416,18 +442,30 @@ class FileDatabaseSelectActivity : StylishActivity(),
/*
* -------------------------
* No Standard Launch, pass by PasswordActivity
* Launch only to standard search, else pass by PasswordActivity
* -------------------------
*/
fun launch(context: Context,
searchInfo: SearchInfo? = null) {
val intent = Intent(context, FileDatabaseSelectActivity::class.java)
searchInfo?.let {
intent.putExtra(KEY_SEARCH_INFO, it)
}
context.startActivity(intent)
}
/*
* -------------------------
* Keyboard Launch
* -------------------------
*/
fun launchForKeyboardSelection(activity: Activity) {
EntrySelectionHelper.startActivityForEntrySelection(activity, Intent(activity, FileDatabaseSelectActivity::class.java))
fun launchForEntrySelectionResult(activity: Activity,
searchInfo: SearchInfo? = null) {
EntrySelectionHelper.startActivityForEntrySelectionResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java),
searchInfo)
}
/*
@@ -439,7 +477,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure,
searchInfo: SearchInfo?) {
searchInfo: SearchInfo? = null) {
AutofillHelper.startActivityForAutofillResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java),
assistStructure,

View File

@@ -47,6 +47,7 @@ import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
@@ -61,8 +62,8 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.model.getSearchString
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
@@ -74,10 +75,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.AddNodeButtonView
import com.kunzisoft.keepass.view.ToolbarAction
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionError
import com.kunzisoft.keepass.view.*
class GroupActivity : LockingActivity(),
GroupEditDialogFragment.EditGroupListener,
@@ -96,7 +94,6 @@ class GroupActivity : LockingActivity(),
private var toolbarAction: ToolbarAction? = null
private var iconView: ImageView? = null
private var numberChildrenView: TextView? = null
private var modeTitleView: TextView? = null
private var addNodeButtonView: AddNodeButtonView? = null
private var groupNameView: TextView? = null
@@ -106,6 +103,9 @@ class GroupActivity : LockingActivity(),
private var mCurrentGroupIsASearch: Boolean = false
private var mRequestStartupSearch = true
// To manage history in selection mode
private var mSelectionModeCountBackStack = 0
// Nodes
private var mRootGroup: Group? = null
private var mCurrentGroup: Group? = null
@@ -135,7 +135,6 @@ class GroupActivity : LockingActivity(),
searchTitleView = findViewById(R.id.search_title)
groupNameView = findViewById(R.id.group_name)
toolbarAction = findViewById(R.id.toolbar_action)
modeTitleView = findViewById(R.id.mode_title_view)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
@@ -145,6 +144,11 @@ class GroupActivity : LockingActivity(),
toolbar?.title = ""
setSupportActionBar(toolbar)
// Retrieve the textColor to tint the icon
val taTextColor = theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
mIconColor = taTextColor.getColor(0, Color.WHITE)
taTextColor.recycle()
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
@@ -171,14 +175,6 @@ class GroupActivity : LockingActivity(),
return
}
// Update last access time.
mCurrentGroup?.touch(modified = false, touchParents = false)
// Retrieve the textColor to tint the icon
val taTextColor = theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
mIconColor = taTextColor.getColor(0, Color.WHITE)
taTextColor.recycle()
var fragmentTag = LIST_NODES_FRAGMENT_TAG
if (mCurrentGroupIsASearch)
fragmentTag = SEARCH_FRAGMENT_TAG
@@ -195,6 +191,14 @@ class GroupActivity : LockingActivity(),
fragmentTag)
.commit()
// Update last access time.
mCurrentGroup?.touch(modified = false, touchParents = false)
// To relaunch the activity with ACTION_SEARCH
if (manageSearchInfoIntent(intent)) {
startActivity(intent)
}
// Add listeners to the add buttons
addNodeButtonView?.setAddGroupClickListener(View.OnClickListener {
GroupEditDialogFragment.build()
@@ -223,6 +227,8 @@ class GroupActivity : LockingActivity(),
newNodes = getListNodesFromBundle(database, newNodesBundle)
}
refreshSearchGroup()
when (actionTask) {
ACTION_DATABASE_UPDATE_GROUP_TASK -> {
if (result.isSuccess) {
@@ -277,11 +283,14 @@ class GroupActivity : LockingActivity(),
super.onNewIntent(intent)
intent?.let { intentNotNull ->
// To transform KEY_SEARCH_INFO in ACTION_SEARCH
manageSearchInfoIntent(intentNotNull)
Log.d(TAG, "setNewIntent: $intentNotNull")
setIntent(intentNotNull)
mCurrentGroupIsASearch = if (Intent.ACTION_SEARCH == intentNotNull.action) {
// only one instance of search in backstack
openSearchGroup(retrieveCurrentGroup(intentNotNull, null))
deletePreviousSearchGroup()
openGroup(retrieveCurrentGroup(intentNotNull, null), true)
true
} else {
false
@@ -289,20 +298,34 @@ class GroupActivity : LockingActivity(),
}
}
private fun openSearchGroup(group: Group?) {
// Delete the previous search fragment
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
if (searchFragment != null) {
if (supportFragmentManager
.popBackStackImmediate(SEARCH_FRAGMENT_TAG,
FragmentManager.POP_BACK_STACK_INCLUSIVE))
supportFragmentManager.beginTransaction().remove(searchFragment).commit()
/**
* Transform the KEY_SEARCH_INFO in ACTION_SEARCH, return true if KEY_SEARCH_INFO was present
*/
private fun manageSearchInfoIntent(intent: Intent): Boolean {
// To relaunch the activity as ACTION_SEARCH
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
val autoSearch = intent.getBooleanExtra(AUTO_SEARCH_KEY, false)
if (searchInfo != null && autoSearch) {
intent.action = Intent.ACTION_SEARCH
intent.putExtra(SearchManager.QUERY, searchInfo.getSearchString(this))
return true
}
openGroup(group, true)
return false
}
private fun openChildGroup(group: Group) {
openGroup(group, false)
private fun deletePreviousSearchGroup() {
// Delete the previous search fragment
try {
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
if (searchFragment != null) {
if (supportFragmentManager
.popBackStackImmediate(SEARCH_FRAGMENT_TAG,
FragmentManager.POP_BACK_STACK_INCLUSIVE))
supportFragmentManager.beginTransaction().remove(searchFragment).commit()
}
} catch (exception: Exception) {
Log.e(TAG, "unable to remove previous search fragment", exception)
}
}
private fun openGroup(group: Group?, isASearch: Boolean) {
@@ -329,6 +352,12 @@ class GroupActivity : LockingActivity(),
fragmentTransaction.addToBackStack(fragmentTag)
fragmentTransaction.commit()
if (mSelectionMode)
mSelectionModeCountBackStack++
// Update last access time.
group?.touch(modified = false, touchParents = false)
mListNodesFragment = newListNodeFragment
mCurrentGroup = group
assignGroupViewElements()
@@ -346,6 +375,12 @@ class GroupActivity : LockingActivity(),
super.onSaveInstanceState(outState)
}
private fun refreshSearchGroup() {
deletePreviousSearchGroup()
if (mCurrentGroupIsASearch)
openGroup(retrieveCurrentGroup(intent, null), true)
}
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): Group? {
// Force read only if the database is like that
@@ -426,13 +461,6 @@ class GroupActivity : LockingActivity(),
// Assign number of children
refreshNumberOfChildren()
// Show selection mode message if needed
if (mSelectionMode) {
modeTitleView?.visibility = View.VISIBLE
} else {
modeTitleView?.visibility = View.GONE
}
// Show button if allowed
addNodeButtonView?.apply {
@@ -450,10 +478,24 @@ class GroupActivity : LockingActivity(),
}
}
override fun onCancelSpecialMode() {
// To remove the navigation history and
EntrySelectionHelper.removeEntrySelectionModeFromIntent(intent)
val fragmentManager = supportFragmentManager
if (mSelectionModeCountBackStack > 0) {
for (selectionMode in 0 .. mSelectionModeCountBackStack) {
fragmentManager.popBackStack()
}
}
// Reinit the counter for navigation history
mSelectionModeCountBackStack = 0
backToTheAppCaller()
}
private fun refreshNumberOfChildren() {
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getNumberOfChildEntries(*Group.ChildFilter.getDefaults(context))?.toString() ?: ""
text = mCurrentGroup?.getNumberOfChildEntries(Group.ChildFilter.getDefaults(context))?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
@@ -468,7 +510,8 @@ class GroupActivity : LockingActivity(),
override fun onNodeClick(node: Node) {
when (node.type) {
Type.GROUP -> try {
openChildGroup(node as Group)
// Open child group
openGroup(node as Group, false)
} catch (e: ClassCastException) {
Log.e(TAG, "Node can't be cast in Group")
}
@@ -480,14 +523,13 @@ class GroupActivity : LockingActivity(),
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
},
{
rebuildListNodes()
// Populate Magikeyboard with entry
mDatabase?.let { database ->
MagikIME.addEntryAndLaunchNotificationIfAllowed(this@GroupActivity,
entryVersioned.getEntryInfo(database))
populateKeyboardAndMoveAppToBackground(this@GroupActivity,
entryVersioned.getEntryInfo(database),
intent)
}
// Consume the selection mode
EntrySelectionHelper.removeEntrySelectionModeFromIntent(intent)
moveTaskToBack(true)
},
{
// Build response with the entry selected
@@ -616,6 +658,7 @@ class GroupActivity : LockingActivity(),
if (database != null
&& database.isRecycleBinEnabled
&& database.recycleBin != mCurrentGroup) {
mProgressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
@@ -667,8 +710,7 @@ class GroupActivity : LockingActivity(),
menu.findItem(R.id.menu_save_database)?.isVisible = false
}
if (!mSelectionMode) {
inflater.inflate(R.menu.default_menu, menu)
MenuUtil.contributionMenuInflater(inflater, menu)
MenuUtil.defaultMenuInflater(inflater, menu)
}
// Menu for recycle bin
@@ -843,7 +885,7 @@ class GroupActivity : LockingActivity(),
removeChildren()
title = name
this.icon = icon // TODO custom icon
this.icon = icon // TODO custom icon #96
}
}
// If group updated save it in the database
@@ -877,19 +919,16 @@ class GroupActivity : LockingActivity(),
}
override fun startActivity(intent: Intent) {
// Get the intent, verify the action and get the query
if (Intent.ACTION_SEARCH == intent.action) {
// manually launch the real search activity
val searchIntent = Intent(applicationContext, GroupActivity::class.java).apply {
// Add bundle of current intent
putExtras(this@GroupActivity.intent)
// manually launch the same search activity
val searchIntent = getIntent().apply {
// add query to the Intent Extras
action = Intent.ACTION_SEARCH
putExtra(SearchManager.QUERY, intent.getStringExtra(SearchManager.QUERY))
}
super.startActivity(searchIntent)
setIntent(searchIntent)
onNewIntent(searchIntent)
} else {
super.startActivity(intent)
}
@@ -921,12 +960,29 @@ class GroupActivity : LockingActivity(),
mListNodesFragment?.onActivityResult(requestCode, resultCode, data)
}
private fun removeSearchInIntent(intent: Intent) {
private fun rebuildListNodes() {
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment?
// to refresh fragment
mListNodesFragment?.rebuildList()
mCurrentGroup = mListNodesFragment?.mainGroup
// Remove search in intent
deletePreviousSearchGroup()
mCurrentGroupIsASearch = false
if (Intent.ACTION_SEARCH == intent.action) {
mCurrentGroupIsASearch = false
intent.action = Intent.ACTION_DEFAULT
intent.removeExtra(SearchManager.QUERY)
}
assignGroupViewElements()
}
private fun backToTheAppCaller() {
if (mAutofillSelection) {
// To get the app caller, only for autofill
super.onBackPressed()
} else {
// To move the app in background
moveTaskToBack(true)
}
}
override fun onBackPressed() {
@@ -934,24 +990,23 @@ class GroupActivity : LockingActivity(),
finishNodeAction()
} else {
// Normal way when we are not in root
if (mRootGroup != null && mRootGroup != mCurrentGroup)
if (mRootGroup != null && mRootGroup != mCurrentGroup) {
super.onBackPressed()
// Else lock if needed
rebuildListNodes()
}
// Else in root, lock if needed
else {
intent.removeExtra(AUTO_SEARCH_KEY)
intent.removeExtra(KEY_SEARCH_INFO)
if (PreferencesUtil.isLockDatabaseWhenBackButtonOnRootClicked(this)) {
lockAndExit()
super.onBackPressed()
} else {
moveTaskToBack(true)
// To restore standard mode
EntrySelectionHelper.removeEntrySelectionModeFromIntent(intent)
backToTheAppCaller()
}
}
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment
// to refresh fragment
mListNodesFragment?.rebuildList()
mCurrentGroup = mListNodesFragment?.mainGroup
removeSearchInIntent(intent)
assignGroupViewElements()
}
}
@@ -964,42 +1019,35 @@ class GroupActivity : LockingActivity(),
private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG"
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
private const val AUTO_SEARCH_KEY = "AUTO_SEARCH_KEY"
private fun buildIntent(context: Context,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(context, GroupActivity::class.java)
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.nodeId)
}
if (searchInfo != null) {
intent.action = Intent.ACTION_SEARCH
val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId
intent.putExtra(SearchManager.QUERY, searchQuery)
}
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intentBuildLauncher.invoke(intent)
}
private fun checkTimeAndBuildIntent(activity: Activity,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
buildIntent(activity, group, searchInfo, readOnly, intentBuildLauncher)
buildIntent(activity, group, readOnly, intentBuildLauncher)
}
}
private fun checkTimeAndBuildIntent(context: Context,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTime(context)) {
buildIntent(context, group, searchInfo, readOnly, intentBuildLauncher)
buildIntent(context, group, readOnly, intentBuildLauncher)
}
}
@@ -1009,8 +1057,14 @@ class GroupActivity : LockingActivity(),
* -------------------------
*/
fun launch(context: Context,
autoSearch: Boolean = false,
searchInfo: SearchInfo? = null,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
checkTimeAndBuildIntent(context, null, readOnly) { intent ->
searchInfo?.let {
intent.putExtra(KEY_SEARCH_INFO, it)
}
intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
context.startActivity(intent)
}
}
@@ -1020,11 +1074,13 @@ class GroupActivity : LockingActivity(),
* Keyboard Launch
* -------------------------
*/
// TODO implement pre search to directly open the direct group
fun launchForKeyboardSelection(context: Context,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
EntrySelectionHelper.startActivityForEntrySelection(context, intent)
fun launchForEntrySelectionResult(context: Context,
autoSearch: Boolean = false,
searchInfo: SearchInfo? = null,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
checkTimeAndBuildIntent(context, null, readOnly) { intent ->
intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
EntrySelectionHelper.startActivityForEntrySelectionResult(context, intent, searchInfo)
}
}
@@ -1036,9 +1092,11 @@ class GroupActivity : LockingActivity(),
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure,
autoSearch: Boolean = false,
searchInfo: SearchInfo? = null,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
checkTimeAndBuildIntent(activity, null, searchInfo, readOnly) { intent ->
checkTimeAndBuildIntent(activity, null, readOnly) { intent ->
intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure, searchInfo)
}
}

View File

@@ -303,7 +303,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
if (readOnly
|| isASearchResult
|| nodes.any { it.type == Type.GROUP }) {
// TODO COPY For Group
// TODO Copy For Group
menu?.removeItem(R.id.menu_copy)
menu?.removeItem(R.id.menu_move)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -17,24 +17,31 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.magikeyboard
package com.kunzisoft.keepass.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.database.search.SearchHelper
class KeyboardLauncherActivity : AppCompatActivity() {
/**
* Activity to select entry in database and populate it in Magikeyboard
*/
class MagikeyboardLauncherActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
if (Database.getInstance().loaded && TimeoutHelper.checkTime(this))
GroupActivity.launchForKeyboardSelection(this)
else {
// Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelection(this)
}
SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(),
null,
{},
{
GroupActivity.launchForEntrySelectionResult(this)
},
{
// Pass extra to get entry
FileDatabaseSelectActivity.launchForEntrySelectionResult(this)
}
)
finish()
super.onCreate(savedInstanceState)
}

View File

@@ -42,18 +42,19 @@ import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
@@ -72,11 +73,10 @@ import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException
open class PasswordActivity : StylishActivity() {
open class PasswordActivity : SpecialModeActivity() {
// Views
private var toolbar: Toolbar? = null
private var containerView: View? = null
private var filenameView: TextView? = null
private var passwordView: EditText? = null
private var keyFileSelectionView: KeyFileSelectionView? = null
@@ -110,6 +110,7 @@ open class PasswordActivity : StylishActivity() {
private var mProgressDialogThread: ProgressDialogThread? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
private var mAllowAutoOpenBiometricPrompt: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -122,7 +123,6 @@ open class PasswordActivity : StylishActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
containerView = findViewById(R.id.container)
confirmButtonView = findViewById(R.id.activity_password_open_button)
filenameView = findViewById(R.id.filename)
passwordView = findViewById(R.id.password)
@@ -166,6 +166,10 @@ open class PasswordActivity : StylishActivity() {
mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
}
if (savedInstanceState?.containsKey(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT) == true) {
mAllowAutoOpenBiometricPrompt = savedInstanceState.getBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT)
}
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
@@ -255,21 +259,52 @@ open class PasswordActivity : StylishActivity() {
}
private fun launchGroupActivity() {
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
EntrySelectionHelper.doEntrySelectionAction(intent,
{
GroupActivity.launch(this@PasswordActivity,
true,
searchInfo,
readOnly)
// Finish activity if no search info
if (searchInfo != null) {
finish()
}
},
{
GroupActivity.launchForKeyboardSelection(this@PasswordActivity,
readOnly)
SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
// Response is build
if (items.size == 1) {
populateKeyboardAndMoveAppToBackground(this@PasswordActivity,
items[0],
intent)
} else {
// Select the one we want
GroupActivity.launchForEntrySelectionResult(this,
true,
searchInfo)
}
},
{
// Here no search info found, disable auto search
GroupActivity.launchForEntrySelectionResult(this@PasswordActivity,
false,
searchInfo,
readOnly)
},
{
// Simply close if database not opened, normally not happened
}
)
// Do not keep history
finish()
},
{ assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
AutofillHelper.checkAutoSearchInfo(this,
SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
@@ -278,10 +313,11 @@ open class PasswordActivity : StylishActivity() {
finish()
},
{
// Here no search info found
// Here no search info found, disable auto search
GroupActivity.launchForAutofillResult(this@PasswordActivity,
assistStructure,
null,
false,
searchInfo,
readOnly)
},
{
@@ -304,33 +340,31 @@ open class PasswordActivity : StylishActivity() {
}
override fun onResume() {
if (Database.getInstance().loaded)
launchGroupActivity()
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
// If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state
if (Database.getInstance().loaded) {
clearCredentialsViews()
}
// For check shutdown
super.onResume()
mProgressDialogThread?.registerProgressTask()
if (Database.getInstance().loaded) {
launchGroupActivity()
} else {
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
initUriFromIntent()
}
// If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state
if (Database.getInstance().loaded) {
clearCredentialsViews()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString())
mProgressDialogThread?.registerProgressTask()
// Don't allow auto open prompt if lock become when UI visible
mAllowAutoOpenBiometricPrompt = if (LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true)
false
else
mAllowAutoOpenBiometricPrompt
initUriFromIntent()
checkPermission()
}
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState)
}
private fun initUriFromIntent() {
@@ -436,6 +470,7 @@ open class PasswordActivity : StylishActivity() {
}
})
}
advancedUnlockedManager?.isBiometricPromptAutoOpenEnable = mAllowAutoOpenBiometricPrompt
advancedUnlockedManager?.checkBiometricAvailability()
biometricInitialize = true
} else {
@@ -497,14 +532,26 @@ open class PasswordActivity : StylishActivity() {
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.destroy()
advancedUnlockedManager = null
}
// Reinit locking activity UI variable
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true
super.onPause()
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.destroy()
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString())
}
super.onDestroy()
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
outState.putBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT, false)
super.onSaveInstanceState(outState)
}
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
@@ -578,14 +625,15 @@ open class PasswordActivity : StylishActivity() {
val inflater = menuInflater
// Read menu
inflater.inflate(R.menu.open_file, menu)
if (mForceReadOnly) {
if (mSelectionMode || mForceReadOnly) {
menu.removeItem(R.id.menu_open_file_read_mode_key)
} else {
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
}
MenuUtil.defaultMenuInflater(inflater, menu)
if (!mSelectionMode) {
MenuUtil.defaultMenuInflater(inflater, menu)
}
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
@@ -594,15 +642,13 @@ open class PasswordActivity : StylishActivity() {
super.onCreateOptionsMenu(menu)
launchEducation(menu) {
launchCheckPermission()
}
launchEducation(menu)
return true
}
// Check permission
private fun launchCheckPermission() {
private fun checkPermission() {
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissions = arrayOf(writePermission)
if (Build.VERSION.SDK_INT >= 23
@@ -632,26 +678,25 @@ open class PasswordActivity : StylishActivity() {
// To fix multiple view education
private var performedEductionInProgress = false
private fun launchEducation(menu: Menu, onEducationFinished: (()-> Unit)? = null) {
private fun launchEducation(menu: Menu) {
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
}
}
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu,
onEducationFinished: (()-> Unit)? = null) {
menu: Menu) {
val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
educationToolbar,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
})
if (!unlockEducationPerformed) {
val readOnlyEducationPerformed =
@@ -660,30 +705,25 @@ open class PasswordActivity : StylishActivity() {
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
{
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
})
if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
val biometricEducationPerformed =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
})
if (!biometricEducationPerformed) {
onEducationFinished?.invoke()
}
}
}
}
@@ -721,6 +761,8 @@ open class PasswordActivity : StylishActivity() {
data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mAllowAutoOpenBiometricPrompt = false
// To get entry in result
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
@@ -739,10 +781,13 @@ open class PasswordActivity : StylishActivity() {
if (!keyFileResult) {
// this block if not a key file response
when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK, Activity.RESULT_CANCELED -> {
LockingActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews()
Database.getInstance().closeAndClear(applicationContext.filesDir)
}
Activity.RESULT_CANCELED -> {
clearCredentialsViews()
}
}
}
}
@@ -760,6 +805,8 @@ open class PasswordActivity : StylishActivity() {
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
private const val ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT = "ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT"
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java)
@@ -779,8 +826,12 @@ open class PasswordActivity : StylishActivity() {
fun launch(
activity: Activity,
databaseFile: Uri,
keyFile: Uri?) {
keyFile: Uri?,
searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
searchInfo?.let {
intent.putExtra(KEY_SEARCH_INFO, it)
}
activity.startActivity(intent)
}
}
@@ -795,9 +846,13 @@ open class PasswordActivity : StylishActivity() {
fun launchForKeyboardResult(
activity: Activity,
databaseFile: Uri,
keyFile: Uri?) {
keyFile: Uri?,
searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
EntrySelectionHelper.startActivityForEntrySelection(activity, intent)
EntrySelectionHelper.startActivityForEntrySelectionResult(
activity,
intent,
searchInfo)
}
}
@@ -824,7 +879,7 @@ open class PasswordActivity : StylishActivity() {
searchInfo)
}
} else {
launch(activity, databaseFile, keyFile)
launch(activity, databaseFile, keyFile, searchInfo)
}
}
}

View File

@@ -28,7 +28,7 @@ import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
class BrowserDialogFragment : DialogFragment() {
class FileManagerDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
@@ -41,15 +41,8 @@ class BrowserDialogFragment : DialogFragment() {
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
textDescription.text = getString(R.string.file_manager_install_description)
val market = root.findViewById<Button>(R.id.file_manager_install_play_store)
market.setOnClickListener {
UriUtil.gotoUrl(context!!, R.string.file_manager_play_store)
dismiss()
}
val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
web.setOnClickListener {
UriUtil.gotoUrl(context!!, R.string.file_manager_f_droid)
root.findViewById<Button>(R.id.file_manager_button).setOnClickListener {
UriUtil.gotoUrl(requireContext(), R.string.file_manager_explanation_url)
dismiss()
}

View File

@@ -167,7 +167,6 @@ class GeneratePasswordDialogFragment : DialogFragment() {
var password = ""
try {
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
password = PasswordGenerator(resources).generatePassword(length,
uppercaseBox?.isChecked == true,
lowercaseBox?.isChecked == true,
@@ -178,6 +177,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
specialsBox?.isChecked == true,
bracketsBox?.isChecked == true,
extendedBox?.isChecked == true)
passwordInputLayoutView?.error = null
} catch (e: NumberFormatException) {
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
} catch (e: IllegalArgumentException) {
@@ -193,7 +193,6 @@ class GeneratePasswordDialogFragment : DialogFragment() {
}
companion object {
const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID"
}
}

View File

@@ -130,9 +130,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
}
iconButtonView?.setOnClickListener { _ ->
fragmentManager?.let {
IconPickerDialogFragment().show(it, "IconPickerDialogFragment")
}
IconPickerDialogFragment().show(parentFragmentManager, "IconPickerDialogFragment")
}
return builder.create()

View File

@@ -24,17 +24,17 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.core.widget.ImageViewCompat
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.GridView
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.ImageViewCompat
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.icons.IconPack
import com.kunzisoft.keepass.icons.IconPackChooser
@@ -60,7 +60,7 @@ class IconPickerDialogFragment : DialogFragment() {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
iconPack = IconPackChooser.getSelectedIconPack(context!!)
iconPack = IconPackChooser.getSelectedIconPack(requireContext())
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
@@ -132,7 +132,7 @@ class IconPickerDialogFragment : DialogFragment() {
return bundle.getParcelable(KEY_ICON_STANDARD)
}
fun launch(activity: StylishActivity) {
fun launch(activity: AppCompatActivity) {
// Create an instance of the dialog fragment and show it
val dialog = IconPickerDialogFragment()
dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment")

View File

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

View File

@@ -24,7 +24,6 @@ import android.os.Build
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.text.Html
import android.text.SpannableStringBuilder
import android.text.method.LinkMovementMethod
import android.widget.TextView

View File

@@ -52,7 +52,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}
@@ -61,7 +61,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(context!!, R.string.contribution_url)
UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
}

View File

@@ -24,15 +24,22 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.model.SearchInfo
object EntrySelectionHelper {
private const val EXTRA_ENTRY_SELECTION_MODE = "com.kunzisoft.keepass.extra.ENTRY_SELECTION_MODE"
private const val DEFAULT_ENTRY_SELECTION_MODE = false
// Key to retrieve search in intent
const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
fun startActivityForEntrySelection(context: Context, intent: Intent) {
fun startActivityForEntrySelectionResult(context: Context,
intent: Intent,
searchInfo: SearchInfo?) {
addEntrySelectionModeExtraInIntent(intent)
// only to avoid visible flickering when redirecting
searchInfo?.let {
intent.putExtra(KEY_SEARCH_INFO, it)
}
context.startActivity(intent)
}

View File

@@ -31,7 +31,7 @@ import android.util.Log
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil
class OpenFileHelper {
@@ -164,11 +164,10 @@ class OpenFileHelper {
*/
private fun showBrowserDialog() {
try {
val browserDialogFragment = BrowserDialogFragment()
if (fragment != null && fragment!!.fragmentManager != null)
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
else
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
val fileManagerDialogFragment = FileManagerDialogFragment()
fragment?.let {
fileManagerDialogFragment.show(it.parentFragmentManager, "browserDialog")
} ?: fileManagerDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}

View File

@@ -19,32 +19,20 @@
*/
package com.kunzisoft.keepass.activities.lock
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
abstract class LockingActivity : StylishActivity() {
companion object {
private const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
}
abstract class LockingActivity : SpecialModeActivity() {
protected var mTimeoutEnable: Boolean = true
@@ -52,11 +40,14 @@ abstract class LockingActivity : StylishActivity() {
private var mExitLock: Boolean = false
// Force readOnly if Entry Selection mode
protected var mReadOnly: Boolean = false
protected var mReadOnly: Boolean
get() {
return field || mSelectionMode
return mReadOnlyToSave || mSelectionMode
}
protected var mSelectionMode: Boolean = false
set(value) {
mReadOnlyToSave = value
}
private var mReadOnlyToSave: Boolean = false
protected var mAutoSaveEnable: Boolean = true
var mProgressDialogThread: ProgressDialogThread? = null
@@ -75,13 +66,17 @@ abstract class LockingActivity : StylishActivity() {
if (mTimeoutEnable) {
mLockReceiver = LockReceiver {
lockAndExit()
closeDatabase()
if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null)
LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE
// Add onActivityForResult response
setResult(RESULT_EXIT_LOCK)
finish()
}
registerLockReceiver(mLockReceiver)
}
mExitLock = false
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
mProgressDialogThread = ProgressDialogThread(this)
}
@@ -102,7 +97,7 @@ abstract class LockingActivity : StylishActivity() {
mProgressDialogThread?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
mReadOnlyToSave = ReadOnlyHelper.retrieveReadOnlyFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
invalidateOptionsMenu()
@@ -122,15 +117,18 @@ abstract class LockingActivity : StylishActivity() {
if (!mExitLock)
TimeoutHelper.recordTime(this)
}
LOCKING_ACTIVITY_UI_VISIBLE = true
}
override fun onSaveInstanceState(outState: Bundle) {
ReadOnlyHelper.onSaveInstanceState(outState, mReadOnly)
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
super.onSaveInstanceState(outState)
}
override fun onPause() {
LOCKING_ACTIVITY_UI_VISIBLE = false
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
@@ -148,7 +146,6 @@ abstract class LockingActivity : StylishActivity() {
protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION))
lock()
}
/**
@@ -179,12 +176,17 @@ abstract class LockingActivity : StylishActivity() {
super.onBackPressed()
}
}
}
fun Activity.lock() {
closeDatabase()
companion object {
// Add onActivityForResult response
setResult(LockingActivity.RESULT_EXIT_LOCK)
finish()
private const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
private var LOCKING_ACTIVITY_UI_VISIBLE = false
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
}
}

View File

@@ -0,0 +1,90 @@
package com.kunzisoft.keepass.activities.selection
import android.os.Build
import android.view.View
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.SpecialModeView
/**
* Activity to manage special mode (ie: selection mode)
*/
abstract class SpecialModeActivity : StylishActivity() {
protected var mSelectionMode: Boolean = false
protected var mAutofillSelection: Boolean = false
private var specialModeView: SpecialModeView? = null
open fun onCancelSpecialMode() {
onBackPressed()
}
override fun onResume() {
super.onResume()
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mAutofillSelection = AutofillHelper.retrieveAssistStructure(intent) != null
}
val searchInfo: SearchInfo? = intent.getParcelableExtra(EntrySelectionHelper.KEY_SEARCH_INFO)
// To show the selection mode
specialModeView = findViewById(R.id.special_mode_view)
specialModeView?.apply {
// Populate title
val typeModeId = if (mAutofillSelection)
R.string.autofill
else
R.string.magic_keyboard_title
title = "${resources.getString(R.string.selection_mode)} (${getString(typeModeId)})"
// Populate subtitle
subtitle = searchInfo?.getName(resources)
// Show the toolbar or not
visible = mSelectionMode
// Add back listener
onCancelButtonClickListener = View.OnClickListener {
onCancelSpecialMode()
}
// Create menu
menu.clear()
if (mAutofillSelection) {
menuInflater.inflate(R.menu.autofill, menu)
setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.menu_block_autofill -> {
blockAutofill(searchInfo)
}
}
true
}
}
}
}
fun blockAutofill(searchInfo: SearchInfo?) {
val webDomain = searchInfo?.webDomain
val applicationId = searchInfo?.applicationId
if (webDomain != null) {
PreferencesUtil.addWebDomainToBlocklist(this,
webDomain)
} else if (applicationId != null) {
PreferencesUtil.addApplicationIdToBlocklist(this,
applicationId)
}
onCancelSpecialMode()
Toast.makeText(this.applicationContext,
R.string.autofill_block_restart,
Toast.LENGTH_LONG).show()
}
}

View File

@@ -41,8 +41,9 @@ abstract class StylishActivity : AppCompatActivity() {
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
intent.component?.let {
if (it.shortClassName == ".HtcLinkifyDispatcherActivity")
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {

View File

@@ -44,8 +44,8 @@ abstract class StylishFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// To fix status bar color
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = activity!!.window
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = requireActivity().window
val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark)
val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark)

View File

@@ -27,6 +27,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
@@ -73,7 +74,11 @@ class NodeAdapter (private val context: Context)
private val mDatabase: Database
@ColorInt
private val contentSelectionColor: Int
@ColorInt
private val iconGroupColor: Int
@ColorInt
private val iconEntryColor: Int
/**
@@ -97,6 +102,10 @@ class NodeAdapter (private val context: Context)
// Database
this.mDatabase = Database.getInstance()
// Color of content selection
val taContentSelectionColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
this.contentSelectionColor = taContentSelectionColor.getColor(0, Color.WHITE)
taContentSelectionColor.recycle()
// Retrieve the color to tint the icon
val taTextColorPrimary = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary))
this.iconGroupColor = taTextColorPrimary.getColor(0, Color.BLACK)
@@ -133,8 +142,7 @@ class NodeAdapter (private val context: Context)
*/
fun rebuildList(group: Group) {
assignPreferences()
nodeSortedList.replaceAll(group.getFilteredChildren(*entryFilters)
)
nodeSortedList.replaceAll(group.getFilteredChildren(entryFilters))
}
private inner class NodeSortedListCallback: SortedListAdapterCallback<Node>(this) {
@@ -281,11 +289,18 @@ class NodeAdapter (private val context: Context)
override fun onBindViewHolder(holder: NodeViewHolder, position: Int) {
val subNode = nodeSortedList.get(position)
// Node selection
holder.container.isSelected = actionNodesList.contains(subNode)
// Assign image
val iconColor = when (subNode.type) {
val iconColor = if (holder.container.isSelected)
contentSelectionColor
else when (subNode.type) {
Type.GROUP -> iconGroupColor
Type.ENTRY -> iconEntryColor
}
holder.imageIdentifier?.setColorFilter(iconColor)
holder.icon.apply {
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
// Relative size of the icon
@@ -331,7 +346,7 @@ class NodeAdapter (private val context: Context)
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group)
.getNumberOfChildEntries(*entryFilters)
.getNumberOfChildEntries(entryFilters)
.toString()
setTextSize(textSizeUnit, numberChildrenTextDefaultDimension, prefSizeMultiplier)
visibility = View.VISIBLE
@@ -348,8 +363,6 @@ class NodeAdapter (private val context: Context)
holder.container.setOnLongClickListener {
nodeClickCallback?.onNodeLongClick(subNode) ?: false
}
holder.container.isSelected = actionNodesList.contains(subNode)
}
override fun getItemCount(): Int {
@@ -373,6 +386,7 @@ class NodeAdapter (private val context: Context)
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var container: View = itemView.findViewById(R.id.node_container)
var imageIdentifier: ImageView? = itemView.findViewById(R.id.node_image_identifier)
var icon: ImageView = itemView.findViewById(R.id.node_icon)
var text: TextView = itemView.findViewById(R.id.node_text)
var subText: TextView = itemView.findViewById(R.id.node_subtext)

View File

@@ -132,7 +132,7 @@ class SearchEntryCursorAdapter(private val context: Context,
val searchGroup = database.createVirtualGroupFromSearch(query, SearchHelper.MAX_SEARCH_ENTRY)
if (searchGroup != null) {
// Search in hide entries but not meta-stream
for (entry in searchGroup.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) {
for (entry in searchGroup.getFilteredChildEntries(Group.ChildFilter.getDefaults(context))) {
entry.entryKDB?.let {
cursorKDB?.addEntry(it)
}

View File

@@ -13,8 +13,6 @@ package com.kunzisoft.keepass.app;
import android.os.Build;
import android.os.Process;
import com.kunzisoft.keepass.utils.StringUtil;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -66,7 +64,7 @@ public final class PRNGFixes {
private static boolean supportedOnThisDevice() {
// Blacklist on samsung devices
if (StringUtil.INSTANCE.indexOfIgnoreCase(Build.MANUFACTURER, "samsung", Locale.ENGLISH) >= 0) {
if (Build.MANUFACTURER.toLowerCase(Locale.ENGLISH).contains("samsung")) {
return false;
}

View File

@@ -34,14 +34,12 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
@RequiresApi(api = Build.VERSION_CODES.O)
@@ -50,7 +48,6 @@ object AutofillHelper {
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
private const val ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
fun retrieveAssistStructure(intent: Intent?): AssistStructure? {
intent?.let {
@@ -122,60 +119,34 @@ object AutofillHelper {
* Build the Autofill response for many entry
*/
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) {
var setResultOk = false
activity.intent?.extras?.let { extras ->
if (extras.containsKey(ASSIST_STRUCTURE)) {
activity.intent?.getParcelableExtra<AssistStructure>(ASSIST_STRUCTURE)?.let { structure ->
StructureParser(structure).parse()?.let { result ->
// New Response
val responseBuilder = FillResponse.Builder()
entriesInfo.forEach {
responseBuilder.addDataset(buildDataset(activity, it, result))
}
val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.")
mReplyIntent.putExtra(
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
responseBuilder.build())
setResultOk = true
activity.setResult(Activity.RESULT_OK, mReplyIntent)
}
}
}
if (!setResultOk) {
Log.w(activity.javaClass.name, "Failed Autofill auth.")
activity.setResult(Activity.RESULT_CANCELED)
}
}
}
/**
* Utility method to perform actions if item is found or not after an auto search in [database]
*/
fun checkAutoSearchInfo(context: Context,
database: Database,
searchInfo: SearchInfo?,
onItemsFound: (items: List<EntryInfo>) -> Unit,
onItemNotFound: () -> Unit,
onDatabaseClosed: () -> Unit) {
if (database.loaded && TimeoutHelper.checkTime(context)) {
var searchWithoutUI = false
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
&& searchInfo != null) {
// If search provide results
database.createVirtualGroupFromSearch(searchInfo, SearchHelper.MAX_SEARCH_ENTRY)?.let { searchGroup ->
if (searchGroup.getNumberOfChildEntries() > 0) {
searchWithoutUI = true
onItemsFound.invoke(
searchGroup.getChildEntriesInfo(database))
}
}
}
if (!searchWithoutUI) {
onItemNotFound.invoke()
}
if (entriesInfo.isEmpty()) {
activity.setResult(Activity.RESULT_CANCELED)
} else {
onDatabaseClosed.invoke()
var setResultOk = false
activity.intent?.extras?.let { extras ->
if (extras.containsKey(ASSIST_STRUCTURE)) {
activity.intent?.getParcelableExtra<AssistStructure>(ASSIST_STRUCTURE)?.let { structure ->
StructureParser(structure).parse()?.let { result ->
// New Response
val responseBuilder = FillResponse.Builder()
entriesInfo.forEach {
responseBuilder.addDataset(buildDataset(activity, it, result))
}
val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.")
mReplyIntent.putExtra(
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
responseBuilder.build())
setResultOk = true
activity.setResult(Activity.RESULT_OK, mReplyIntent)
}
}
}
if (!setResultOk) {
Log.w(activity.javaClass.name, "Failed Autofill auth.")
activity.setResult(Activity.RESULT_CANCELED)
}
}
}
}

View File

@@ -26,12 +26,25 @@ import android.util.Log
import android.widget.RemoteViews
import androidx.annotation.RequiresApi
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
@RequiresApi(api = Build.VERSION_CODES.O)
class KeeAutofillService : AutofillService() {
var applicationIdBlocklist: Set<String>? = null
var webDomainBlocklist: Set<String>? = null
override fun onCreate() {
super.onCreate()
applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this)
webDomainBlocklist = PreferencesUtil.webDomainBlocklist(this)
}
override fun onFillRequest(request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback) {
@@ -43,32 +56,36 @@ class KeeAutofillService : AutofillService() {
// Check user's settings for authenticating Responses and Datasets.
StructureParser(latestStructure).parse()?.let { parseResult ->
val searchInfo = SearchInfo().apply {
applicationId = parseResult.applicationId
webDomain = parseResult.domain
}
// Build search info only if applicationId or webDomain are not blocked
if (searchAllowedFor(parseResult.applicationId, applicationIdBlocklist)
&& searchAllowedFor(parseResult.domain, webDomainBlocklist)) {
val searchInfo = SearchInfo().apply {
applicationId = parseResult.applicationId
webDomain = parseResult.domain
}
AutofillHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
val responseBuilder = FillResponse.Builder()
AutofillHelper.addHeader(responseBuilder, packageName,
parseResult.domain, parseResult.applicationId)
items.forEach {
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
SearchHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
val responseBuilder = FillResponse.Builder()
AutofillHelper.addHeader(responseBuilder, packageName,
parseResult.domain, parseResult.applicationId)
items.forEach {
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
}
callback.onSuccess(responseBuilder.build())
},
{
// Show UI if no search result
showUIForEntrySelection(parseResult, searchInfo, callback)
},
{
// Show UI if database not open
showUIForEntrySelection(parseResult, searchInfo, callback)
}
callback.onSuccess(responseBuilder.build())
},
{
// Show UI if no search result
showUIForEntrySelection(parseResult, searchInfo, callback)
},
{
// Show UI if database not open
showUIForEntrySelection(parseResult, searchInfo, callback)
}
)
)
}
}
}
@@ -114,5 +131,18 @@ class KeeAutofillService : AutofillService() {
companion object {
private val TAG = KeeAutofillService::class.java.name
fun searchAllowedFor(element: String?, blockList: Set<String>?): Boolean {
element?.let { elementNotNull ->
if (blockList?.any { appIdBlocked ->
elementNotNull.contains(appIdBlocked)
} == true
) {
Log.d(TAG, "Autofill not allowed for $elementNotNull")
return false
}
}
return true
}
}
}

View File

@@ -35,6 +35,7 @@ import java.util.*
internal class StructureParser(private val structure: AssistStructure) {
private var result: Result? = null
private var usernameCandidate: AutofillId? = null
private var usernameNeeded = true
fun parse(): Result? {
try {
@@ -55,7 +56,7 @@ internal class StructureParser(private val structure: AssistStructure) {
}
// Return the result only if password field is retrieved
return if (result?.usernameId != null
return if ((!usernameNeeded || result?.usernameId != null)
&& result?.passwordId != null)
result
else
@@ -101,8 +102,12 @@ internal class StructureParser(private val structure: AssistStructure) {
when {
it.equals(View.AUTOFILL_HINT_USERNAME, true)
|| it.equals(View.AUTOFILL_HINT_EMAIL_ADDRESS, true)
|| it.equals("email", true)
|| it.equals(View.AUTOFILL_HINT_PHONE, true)
|| it.equals("usernameOrEmail", true)-> {
|| it.contains("OrUsername", true)
|| it.contains("OrEmailAddress", true)
|| it.contains("OrEmail", true)
|| it.contains("OrPhone", true)-> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username hint")
}
@@ -110,6 +115,8 @@ internal class StructureParser(private val structure: AssistStructure) {
|| it.contains("password", true) -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password hint")
// Username not needed in this case
usernameNeeded = false
return true
}
// Ignore autocomplete="off"
@@ -156,42 +163,78 @@ internal class StructureParser(private val structure: AssistStructure) {
return false
}
private fun inputIsVariationType(inputType: Int, vararg type: Int): Boolean {
type.forEach {
if (inputType and InputType.TYPE_MASK_VARIATION == it)
return true
}
return false
}
private fun showHexInputType(inputType: Int): String {
return "0x${"%08x".format(inputType)}"
}
private fun parseNodeByAndroidInput(node: AssistStructure.ViewNode): Boolean {
val autofillId = node.autofillId
val inputType = node.inputType
if (inputType and InputType.TYPE_CLASS_TEXT != 0) {
when {
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != 0 -> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username android type: $inputType")
when (inputType and InputType.TYPE_MASK_CLASS) {
InputType.TYPE_CLASS_TEXT -> {
when {
inputIsVariationType(inputType,
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS) -> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username android text type: ${showHexInputType(inputType)}")
}
inputIsVariationType(inputType,
InputType.TYPE_TEXT_VARIATION_NORMAL,
InputType.TYPE_TEXT_VARIATION_PERSON_NAME,
InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) -> {
usernameCandidate = autofillId
Log.d(TAG, "Autofill username candidate android text type: ${showHexInputType(inputType)}")
}
inputIsVariationType(inputType,
InputType.TYPE_TEXT_VARIATION_PASSWORD,
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password android text type: ${showHexInputType(inputType)}")
usernameNeeded = false
return true
}
inputIsVariationType(inputType,
InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT,
InputType.TYPE_TEXT_VARIATION_FILTER,
InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE,
InputType.TYPE_TEXT_VARIATION_PHONETIC,
InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS,
InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE,
InputType.TYPE_TEXT_VARIATION_URI) -> {
// Type not used
}
else -> {
Log.d(TAG, "Autofill unknown android text type: ${showHexInputType(inputType)}")
}
}
inputType and InputType.TYPE_TEXT_VARIATION_NORMAL != 0 ||
inputType and InputType.TYPE_NUMBER_VARIATION_NORMAL != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_PERSON_NAME != 0 -> {
usernameCandidate = autofillId
Log.d(TAG, "Autofill username candidate android type: $inputType")
}
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != 0 ||
inputType and InputType.TYPE_NUMBER_VARIATION_PASSWORD != 0 -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password android type: $inputType")
return true
}
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_FILTER != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_PHONETIC != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_URI != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != 0 -> {
// Type not used
}
else -> {
Log.d(TAG, "Autofill unknown android type: $inputType")
usernameCandidate = autofillId
}
InputType.TYPE_CLASS_NUMBER -> {
when {
inputIsVariationType(inputType,
InputType.TYPE_NUMBER_VARIATION_NORMAL) -> {
usernameCandidate = autofillId
Log.d(TAG, "Autofill usernale candidate android number type: ${showHexInputType(inputType)}")
}
inputIsVariationType(inputType,
InputType.TYPE_NUMBER_VARIATION_PASSWORD) -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password android number type: ${showHexInputType(inputType)}")
usernameNeeded = false
return true
}
else -> {
Log.d(TAG, "Autofill unknown android number type: ${showHexInputType(inputType)}")
}
}
}
}

View File

@@ -19,15 +19,11 @@
*/
package com.kunzisoft.keepass.backup
import android.annotation.SuppressLint
import android.app.backup.BackupAgentHelper
import android.app.backup.SharedPreferencesBackupHelper
@SuppressLint("NewApi")
class SettingsBackupAgent : BackupAgentHelper() {
//TODO Backup
override fun onCreate() {
val defaultPrefs = this.packageName + "_preferences"
val prefHelper = SharedPreferencesBackupHelper(this, defaultPrefs)

View File

@@ -52,7 +52,11 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
private var biometricMode: Mode = Mode.UNAVAILABLE
private var isBiometricPromptAutoOpenEnable = PreferencesUtil.isBiometricPromptAutoOpenEnable(context)
private var biometricPromptAutoOpenPreference = PreferencesUtil.isBiometricPromptAutoOpenEnable(context)
var isBiometricPromptAutoOpenEnable: Boolean = true
get() {
return field && biometricPromptAutoOpenPreference
}
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
@@ -272,6 +276,8 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
fun destroy() {
// Close the biometric prompt
biometricUnlockDatabaseHelper?.closeBiometricPrompt()
// Restore the checked listener
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
}
@@ -300,7 +306,6 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec)
// TODO setAdvancedUnlockedMessageView(R.string.encrypted_value_stored)
}
override fun handleDecryptedResult(decryptedValue: String) {

View File

@@ -60,7 +60,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
setConfirmationRequired(true)
// TODO device credential
// TODO device credential #102 #152
/*
if (keyguardManager?.isDeviceSecure == true)
setDeviceCredentialAllowed(true)
@@ -73,7 +73,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
setConfirmationRequired(false)
// TODO device credential
// TODO device credential #102 #152
/*
if (keyguardManager?.isDeviceSecure == true)
setDeviceCredentialAllowed(true)
@@ -271,6 +271,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
}
}
fun closeBiometricPrompt() {
biometricPrompt?.cancelAuthentication()
}
interface BiometricUnlockErrorCallback {
fun onInvalidKeyException(e: Exception)
fun onBiometricException(e: Exception)

View File

@@ -24,7 +24,7 @@ import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
import org.spongycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.NoSuchAlgorithmException
import java.security.Security
import java.util.*
@@ -37,20 +37,10 @@ object CipherFactory {
private var blacklisted: Boolean = false
init {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class)
@JvmOverloads
fun getInstance(transformation: String, androidOverride: Boolean = false): Cipher {
// Return the native AES if it is possible
return if (!deviceBlacklisted() && !androidOverride && hasNativeImplementation(transformation) && NativeLib.loaded()) {
Cipher.getInstance(transformation, AESProvider())
} else {
Cipher.getInstance(transformation)
}
}
fun deviceBlacklisted(): Boolean {
if (!blacklistInit) {
blacklistInit = true
@@ -64,6 +54,16 @@ object CipherFactory {
return transformation == "AES/CBC/PKCS5Padding"
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class)
fun getInstance(transformation: String, androidOverride: Boolean = false): Cipher {
// Return the native AES if it is possible
return if (!deviceBlacklisted() && !androidOverride && hasNativeImplementation(transformation) && NativeLib.loaded()) {
Cipher.getInstance(transformation, AESProvider())
} else {
Cipher.getInstance(transformation)
}
}
/**
* Generate appropriate cipher based on KeePass 2.x UUID's
*/

View File

@@ -19,16 +19,18 @@
*/
package com.kunzisoft.keepass.crypto
enum class CrsAlgorithm constructor(val id: Int) {
import com.kunzisoft.keepass.utils.UnsignedInt
Null(0),
ArcFourVariant(1),
Salsa20(2),
ChaCha20(3);
enum class CrsAlgorithm constructor(val id: UnsignedInt) {
Null(UnsignedInt(0)),
ArcFourVariant(UnsignedInt(1)),
Salsa20(UnsignedInt(2)),
ChaCha20(UnsignedInt(3));
companion object {
fun fromId(num: Int): CrsAlgorithm? {
fun fromId(num: UnsignedInt): CrsAlgorithm? {
for (e in values()) {
if (e.id == num) {
return e

View File

@@ -74,12 +74,10 @@ object CryptoUtil {
return ret
}
@JvmOverloads
fun hashSha256(data: ByteArray, offset: Int = 0, count: Int = data.size): ByteArray {
return hashGen("SHA-256", data, offset, count)
}
@JvmOverloads
fun hashSha512(data: ByteArray, offset: Int = 0, count: Int = data.size): ByteArray {
return hashGen("SHA-512", data, offset, count)
}

View File

@@ -19,11 +19,11 @@
*/
package com.kunzisoft.keepass.crypto
import org.spongycastle.crypto.StreamCipher
import org.spongycastle.crypto.engines.ChaCha7539Engine
import org.spongycastle.crypto.engines.Salsa20Engine
import org.spongycastle.crypto.params.KeyParameter
import org.spongycastle.crypto.params.ParametersWithIV
import org.bouncycastle.crypto.StreamCipher
import org.bouncycastle.crypto.engines.ChaCha7539Engine
import org.bouncycastle.crypto.engines.Salsa20Engine
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.crypto.params.ParametersWithIV
object StreamCipherFactory {

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.spongycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException

View File

@@ -17,18 +17,20 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;
package com.kunzisoft.keepass.crypto.finalkey
import com.kunzisoft.keepass.crypto.CipherFactory;
import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
public class FinalKeyFactory {
public static FinalKey createFinalKey() {
object AESKeyTransformerFactory : KeyTransformer() {
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
// Prefer the native final key implementation
if ( !CipherFactory.INSTANCE.deviceBlacklisted() && NativeFinalKey.available() ) {
return new NativeFinalKey();
val keyTransformer = if (!deviceBlacklisted()
&& NativeAESKeyTransformer.available()) {
NativeAESKeyTransformer()
} else {
// Fall back on the android crypto implementation
return new AndroidFinalKey();
AndroidAESKeyTransformer()
}
return keyTransformer.transformMasterKey(seed, key, rounds)
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey
import java.io.IOException
import java.lang.Exception
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.ShortBufferException
import javax.crypto.spec.SecretKeySpec
class AndroidAESKeyTransformer : KeyTransformer() {
@Throws(IOException::class)
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
val cipher: Cipher = try {
Cipher.getInstance("AES/ECB/NoPadding")
} catch (e: Exception) {
throw IOException("Unable to get the cipher", e)
}
try {
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(seed, "AES"))
} catch (e: InvalidKeyException) {
throw IOException("Unable to init the cipher", e)
}
if (key == null) {
throw IOException("Invalid key")
}
if (rounds == null) {
throw IOException("Invalid rounds")
}
// Encrypt key rounds times
val keyLength = key.size
val newKey = ByteArray(keyLength)
System.arraycopy(key, 0, newKey, 0, keyLength)
val destKey = ByteArray(keyLength)
for (i in 0 until rounds) {
try {
cipher.update(newKey, 0, newKey.size, destKey, 0)
System.arraycopy(destKey, 0, newKey, 0, newKey.size)
} catch (e: ShortBufferException) {
throw IOException("Short buffer", e)
}
}
// Hash the key
val messageDigest: MessageDigest = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here: " + e.message)
}
messageDigest.update(newKey)
return messageDigest.digest()
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
public class AndroidFinalKey extends FinalKey {
@Override
public byte[] transformMasterKey(byte[] pKeySeed, byte[] pKey, long rounds) throws IOException {
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/ECB/NoPadding");
} catch (NoSuchAlgorithmException e) {
throw new IOException("NoSuchAlgorithm: " + e.getMessage());
} catch (NoSuchPaddingException e) {
throw new IOException("NoSuchPadding: " + e.getMessage());
}
try {
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pKeySeed, "AES"));
} catch (InvalidKeyException e) {
throw new IOException("InvalidPasswordException: " + e.getMessage());
}
// Encrypt key rounds times
byte[] newKey = new byte[pKey.length];
System.arraycopy(pKey, 0, newKey, 0, pKey.length);
byte[] destKey = new byte[pKey.length];
for (int i = 0; i < rounds; i++) {
try {
cipher.update(newKey, 0, newKey.length, destKey, 0);
System.arraycopy(destKey, 0, newKey, 0, newKey.length);
} catch (ShortBufferException e) {
throw new IOException("Short buffer: " + e.getMessage());
}
}
// Hash the key
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
assert true;
throw new IOException("SHA-256 not implemented here: " + e.getMessage());
}
md.update(newKey);
return md.digest();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -17,10 +17,11 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;
package com.kunzisoft.keepass.crypto.finalkey
import java.io.IOException;
import java.io.IOException
public abstract class FinalKey {
public abstract byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException;
}
abstract class KeyTransformer {
@Throws(IOException::class)
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray?
}

View File

@@ -21,17 +21,20 @@ package com.kunzisoft.keepass.crypto.finalkey;
import com.kunzisoft.keepass.crypto.NativeLib;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
public class NativeFinalKey extends FinalKey {
public class NativeAESKeyTransformer extends KeyTransformer {
public static boolean available() {
return NativeLib.INSTANCE.init();
}
@Nullable
@Override
public byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException {
public byte[] transformMasterKey(@Nullable byte[] seed, @Nullable byte[] key, @Nullable Long rounds) throws IOException {
NativeLib.INSTANCE.init();
return nTransformMasterKey(seed, key, rounds);

View File

@@ -22,72 +22,69 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
class AesKdf internal constructor() : KdfEngine() {
class AesKdf : KdfEngine() {
init {
uuid = CIPHER_UUID
}
override val defaultParameters: KdfParameters
get() {
return KdfParameters(uuid!!).apply {
setParamUUID()
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
setUInt64(PARAM_ROUNDS, defaultKeyRounds)
}
}
override val defaultKeyRounds: Long
get() = DEFAULT_ROUNDS.toLong()
init {
uuid = CIPHER_UUID
}
override val defaultKeyRounds: Long = 6000L
override fun getName(resources: Resources): String {
return resources.getString(R.string.kdf_AES)
}
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
var currentMasterKey = masterKey
val rounds = p.getUInt64(PARAM_ROUNDS)
var seed = p.getByteArray(PARAM_SEED)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
var seed = kdfParameters.getByteArray(PARAM_SEED)
if (seed != null && seed.size != 32) {
seed = CryptoUtil.hashSha256(seed)
}
var currentMasterKey = masterKey
if (currentMasterKey.size != 32) {
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
}
if (seed.size != 32) {
seed = CryptoUtil.hashSha256(seed)
}
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)
val key = FinalKeyFactory.createFinalKey()
return key.transformMasterKey(seed, currentMasterKey, rounds)
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
}
override fun randomize(p: KdfParameters) {
override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom()
val seed = ByteArray(32)
random.nextBytes(seed)
p.setByteArray(PARAM_SEED, seed)
kdfParameters.setByteArray(PARAM_SEED, seed)
}
override fun getKeyRounds(p: KdfParameters): Long {
return p.getUInt64(PARAM_ROUNDS)
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds
}
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
p.setUInt64(PARAM_ROUNDS, keyRounds)
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds)
}
companion object {
private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xC9.toByte(),
0xD9.toByte(),
@@ -106,7 +103,7 @@ class AesKdf internal constructor() : KdfEngine() {
0x4F.toByte(),
0xEA.toByte()))
const val PARAM_ROUNDS = "R"
const val PARAM_SEED = "S"
const val PARAM_ROUNDS = "R" // UInt64
const val PARAM_SEED = "S" // Byte array
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -53,35 +54,49 @@ class Argon2Kdf internal constructor() : KdfEngine() {
}
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
val salt = p.getByteArray(PARAM_SALT)
val parallelism = p.getUInt32(PARAM_PARALLELISM).toInt()
val memory = p.getUInt64(PARAM_MEMORY)
val iterations = p.getUInt64(PARAM_ITERATIONS)
val version = p.getUInt32(PARAM_VERSION)
val secretKey = p.getByteArray(PARAM_SECRET_KEY)
val assocData = p.getByteArray(PARAM_ASSOC_DATA)
val salt = kdfParameters.getByteArray(PARAM_SALT)
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
UnsignedInt(it)
}
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let {
UnsignedInt.fromLong(it)
}
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.let {
UnsignedInt.fromLong(it)
}
val version = kdfParameters.getUInt32(PARAM_VERSION)?.let {
UnsignedInt(it)
}
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
return Argon2Native.transformKey(masterKey, salt, parallelism, memory, iterations,
secretKey, assocData, version)
return Argon2Native.transformKey(masterKey,
salt,
parallelism,
memory,
iterations,
secretKey,
assocData,
version)
}
override fun randomize(p: KdfParameters) {
override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom()
val salt = ByteArray(32)
random.nextBytes(salt)
p.setByteArray(PARAM_SALT, salt)
kdfParameters.setByteArray(PARAM_SALT, salt)
}
override fun getKeyRounds(p: KdfParameters): Long {
return p.getUInt64(PARAM_ITERATIONS)
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds
}
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
p.setUInt64(PARAM_ITERATIONS, keyRounds)
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds)
}
override val minKeyRounds: Long
@@ -90,12 +105,12 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxKeyRounds: Long
get() = MAX_ITERATIONS
override fun getMemoryUsage(p: KdfParameters): Long {
return p.getUInt64(PARAM_MEMORY)
override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage
}
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
p.setUInt64(PARAM_MEMORY, memory)
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
kdfParameters.setUInt64(PARAM_MEMORY, memory)
}
override val defaultMemoryUsage: Long
@@ -107,21 +122,23 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val maxMemoryUsage: Long
get() = MAX_MEMORY
override fun getParallelism(p: KdfParameters): Int {
return p.getUInt32(PARAM_PARALLELISM).toInt() // TODO Verify
override fun getParallelism(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
UnsignedInt(it).toLong()
} ?: defaultParallelism
}
override fun setParallelism(p: KdfParameters, parallelism: Int) {
p.setUInt32(PARAM_PARALLELISM, parallelism.toLong())
override fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
}
override val defaultParallelism: Int
get() = DEFAULT_PARALLELISM.toInt()
override val defaultParallelism: Long
get() = DEFAULT_PARALLELISM.toLong()
override val minParallelism: Int
override val minParallelism: Long
get() = MIN_PARALLELISM
override val maxParallelism: Int
override val maxParallelism: Long
get() = MAX_PARALLELISM
companion object {
@@ -152,23 +169,24 @@ class Argon2Kdf internal constructor() : KdfEngine() {
private const val PARAM_SECRET_KEY = "K" // byte[]
private const val PARAM_ASSOC_DATA = "A" // byte[]
private const val MIN_VERSION: Long = 0x10
private const val MAX_VERSION: Long = 0x13
private val MIN_VERSION = UnsignedInt(0x10)
private val MAX_VERSION = UnsignedInt(0x13)
private const val MIN_SALT = 8
private const val MAX_SALT = Integer.MAX_VALUE
private val MAX_SALT = UnsignedInt.MAX_VALUE.toLong()
private const val MIN_ITERATIONS: Long = 1
private const val MIN_ITERATIONS: Long = 1L
private const val MAX_ITERATIONS = 4294967295L
private const val MIN_MEMORY = (1024 * 8).toLong()
private const val MAX_MEMORY = Integer.MAX_VALUE.toLong()
private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toLong()
private const val MEMORY_BLOCK_SIZE: Long = 1024L
private const val MIN_PARALLELISM = 1
private const val MAX_PARALLELISM = (1 shl 24) - 1
private const val MIN_PARALLELISM: Long = 1L
private const val MAX_PARALLELISM: Long = ((1 shl 24) - 1).toLong()
private const val DEFAULT_ITERATIONS: Long = 2
private const val DEFAULT_ITERATIONS: Long = 2L
private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
private const val DEFAULT_PARALLELISM: Long = 2
private val DEFAULT_PARALLELISM = UnsignedInt(2)
}
}

View File

@@ -20,20 +20,29 @@
package com.kunzisoft.keepass.crypto.keyDerivation;
import com.kunzisoft.keepass.crypto.NativeLib;
import com.kunzisoft.keepass.utils.UnsignedInt;
import java.io.IOException;
public class Argon2Native {
public static byte[] transformKey(byte[] password, byte[] salt, int parallelism,
long memory, long iterations, byte[] secretKey,
byte[] associatedData, long version) throws IOException {
public static byte[] transformKey(byte[] password, byte[] salt, UnsignedInt parallelism,
UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
byte[] associatedData, UnsignedInt version) throws IOException {
NativeLib.INSTANCE.init();
return nTransformMasterKey(password, salt, parallelism, memory, iterations, secretKey, associatedData, version);
return nTransformMasterKey(
password,
salt,
parallelism.toInt(),
memory.toInt(),
iterations.toInt(),
secretKey,
associatedData,
version.toInt());
}
private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism,
long memory, long iterations, byte[] secretKey,
byte[] associatedData, long version) throws IOException;
int memory, int iterations, byte[] secretKey,
byte[] associatedData, int version) throws IOException;
}

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.Serializable
@@ -33,17 +34,17 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
abstract val defaultParameters: KdfParameters
@Throws(IOException::class)
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray
abstract fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray
abstract fun randomize(p: KdfParameters)
abstract fun randomize(kdfParameters: KdfParameters)
/*
* ITERATIONS
*/
abstract fun getKeyRounds(p: KdfParameters): Long
abstract fun getKeyRounds(kdfParameters: KdfParameters): Long
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long)
abstract fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long)
abstract val defaultKeyRounds: Long
@@ -51,51 +52,51 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
get() = 1
open val maxKeyRounds: Long
get() = Int.MAX_VALUE.toLong()
get() = UnsignedInt.MAX_VALUE.toLong()
/*
* MEMORY
*/
open fun getMemoryUsage(p: KdfParameters): Long {
return UNKNOWN_VALUE.toLong()
open fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE
}
open fun setMemoryUsage(p: KdfParameters, memory: Long) {
open fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
// Do nothing by default
}
open val defaultMemoryUsage: Long
get() = UNKNOWN_VALUE.toLong()
get() = UNKNOWN_VALUE
open val minMemoryUsage: Long
get() = 1
open val maxMemoryUsage: Long
get() = Int.MAX_VALUE.toLong()
get() = UnsignedInt.MAX_VALUE.toLong()
/*
* PARALLELISM
*/
open fun getParallelism(p: KdfParameters): Int {
open fun getParallelism(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE
}
open fun setParallelism(p: KdfParameters, parallelism: Int) {
open fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
// Do nothing by default
}
open val defaultParallelism: Int
open val defaultParallelism: Long
get() = UNKNOWN_VALUE
open val minParallelism: Int
get() = 1
open val minParallelism: Long
get() = 1L
open val maxParallelism: Int
get() = Int.MAX_VALUE
open val maxParallelism: Long
get() = UnsignedInt.MAX_VALUE.toLong()
companion object {
const val UNKNOWN_VALUE = -1
const val UNKNOWN_VALUE: Long = -1L
}
}

View File

@@ -29,7 +29,7 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.*
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
class KdfParameters(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() {
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
@@ -41,26 +41,25 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
@Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data)
val lis = LittleEndianDataInputStream(bis)
val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data))
val dictionary = deserialize(inputStream)
val d = deserialize(lis) ?: return null
val uuidBytes = dictionary.getByteArray(PARAM_UUID) ?: return null
val uuid = bytes16ToUuid(uuidBytes)
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
val kdfP = KdfParameters(uuid)
kdfP.copyTo(d)
return kdfP
val kdfParameters = KdfParameters(uuid)
kdfParameters.copyTo(dictionary)
return kdfParameters
}
@Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream()
val los = LittleEndianDataOutputStream(bos)
fun serialize(kdfParameters: KdfParameters): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream()
val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream)
serialize(kdf, los)
serialize(kdfParameters, outputStream)
return bos.toByteArray()
return byteArrayOutputStream.toByteArray()
}
}

View File

@@ -30,7 +30,8 @@ import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
@@ -54,6 +55,8 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
@@ -61,12 +64,10 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.retrieveProgressDialog
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
@@ -85,26 +86,25 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
private var serviceConnection: ServiceConnection? = null
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
TimeoutHelper.temporarilyDisableTimeout()
// Stop the opening notification
DatabaseOpenNotificationService.stop(activity)
startOrUpdateDialog(titleId, messageId, warningId)
startDialog(titleId, messageId, warningId)
}
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
TimeoutHelper.temporarilyDisableTimeout()
// Stop the opening notification
DatabaseOpenNotificationService.stop(activity)
startOrUpdateDialog(titleId, messageId, warningId)
updateDialog(titleId, messageId, warningId)
}
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(actionTask, result)
// Remove the progress task
ProgressTaskDialogFragment.stop(activity)
stopDialog()
TimeoutHelper.releaseTemporarilyDisableTimeout()
val inTime = if (activity is LockingActivity) {
@@ -120,13 +120,22 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
}
}
private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
var progressTaskDialogFragment = retrieveProgressDialog(activity)
private fun startDialog(titleId: Int? = null,
messageId: Int? = null,
warningId: Int? = null) {
if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = ProgressTaskDialogFragment.build()
ProgressTaskDialogFragment.start(activity, progressTaskDialogFragment)
progressTaskDialogFragment = activity.supportFragmentManager
.findFragmentByTag(PROGRESS_TASK_DIALOG_TAG) as ProgressTaskDialogFragment?
}
progressTaskDialogFragment.apply {
if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = ProgressTaskDialogFragment()
progressTaskDialogFragment?.show(activity.supportFragmentManager, PROGRESS_TASK_DIALOG_TAG)
}
updateDialog(titleId, messageId, warningId)
}
private fun updateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
progressTaskDialogFragment?.apply {
titleId?.let {
updateTitle(it)
}
@@ -139,7 +148,11 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
}
}
@Synchronized
private fun stopDialog() {
progressTaskDialogFragment?.dismissAllowingStateLoss()
progressTaskDialogFragment = null
}
private fun initServiceConnection() {
if (serviceConnection == null) {
serviceConnection = object : ServiceConnection {
@@ -158,7 +171,6 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
}
}
@Synchronized
private fun bindService() {
initServiceConnection()
serviceConnection?.let {
@@ -169,7 +181,6 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
/**
* Unbind the service and assign null to the service connection to check if already unbind or not
*/
@Synchronized
private fun unBindService() {
serviceConnection?.let {
activity.unbindService(it)
@@ -177,22 +188,21 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
serviceConnection = null
}
@Synchronized
fun registerProgressTask() {
ProgressTaskDialogFragment.stop(activity)
stopDialog()
// Register a database task receiver to stop loading dialog when service finish the task
databaseTaskBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
activity.runOnUiThread {
when (intent?.action) {
DATABASE_START_TASK_ACTION -> {
// Bind to the service when is starting
bindService()
}
DATABASE_STOP_TASK_ACTION -> {
unBindService()
}
when (intent?.action) {
DATABASE_START_TASK_ACTION -> {
// Bind to the service when is starting
bindService()
}
DATABASE_STOP_TASK_ACTION -> {
// Remove the progress task
stopDialog()
unBindService()
}
}
}
@@ -208,9 +218,8 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
bindService()
}
@Synchronized
fun unregisterProgressTask() {
ProgressTaskDialogFragment.stop(activity)
stopDialog()
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder = null
@@ -224,18 +233,15 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
}
}
@Synchronized
private fun start(bundle: Bundle? = null, actionTask: String) {
activity.stopService(intentDatabaseTask)
if (bundle != null)
intentDatabaseTask.putExtras(bundle)
activity.runOnUiThread {
intentDatabaseTask.action = actionTask
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(intentDatabaseTask)
} else {
activity.startService(intentDatabaseTask)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(intentDatabaseTask)
} else {
activity.startService(intentDatabaseTask)
}
}
@@ -552,12 +558,12 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
}
fun startDatabaseSaveParallelism(oldParallelism: Int,
newParallelism: Int,
fun startDatabaseSaveParallelism(oldParallelism: Long,
newParallelism: Long,
save: Boolean) {
start(Bundle().apply {
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK)

View File

@@ -44,13 +44,13 @@ import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.stream.readBytes4ToInt
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil
import org.apache.commons.io.FileUtils
import java.io.*
import java.util.*
@@ -205,7 +205,6 @@ class Database {
var numberKeyEncryptionRounds: Long
get() = mDatabaseKDB?.numberKeyEncryptionRounds ?: mDatabaseKDBX?.numberKeyEncryptionRounds ?: 0
@Throws(NumberFormatException::class)
set(numberRounds) {
mDatabaseKDB?.numberKeyEncryptionRounds = numberRounds
mDatabaseKDBX?.numberKeyEncryptionRounds = numberRounds
@@ -213,13 +212,13 @@ class Database {
var memoryUsage: Long
get() {
return mDatabaseKDBX?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE.toLong()
return mDatabaseKDBX?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE
}
set(memory) {
mDatabaseKDBX?.memoryUsage = memory
}
var parallelism: Int
var parallelism: Long
get() = mDatabaseKDBX?.parallelism ?: KdfEngine.UNKNOWN_VALUE
set(parallelism) {
mDatabaseKDBX?.parallelism = parallelism
@@ -338,7 +337,10 @@ class Database {
}
// Load Data, pass Uris as InputStreams
databaseInputStream = BufferedInputStream(UriUtil.getUriInputStream(contentResolver, uri))
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
?: throw IOException("Database input stream cannot be retrieve")
databaseInputStream = BufferedInputStream(databaseStream)
if (!databaseInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
@@ -347,8 +349,8 @@ class Database {
databaseInputStream.mark(10)
// Get the file directory to save the attachments
val sig1 = databaseInputStream.readBytes4ToInt()
val sig2 = databaseInputStream.readBytes4ToInt()
val sig1 = databaseInputStream.readBytes4ToUInt()
val sig2 = databaseInputStream.readBytes4ToUInt()
// Return to the start
databaseInputStream.reset()
@@ -397,17 +399,24 @@ class Database {
false
}
fun createVirtualGroupFromSearch(searchQuery: String, max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchQuery, max)
fun createVirtualGroupFromSearch(searchQuery: String,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchQuery, SearchParameters(), max)
}
fun createVirtualGroupFromSearch(searchInfo: SearchInfo, max: Int = Integer.MAX_VALUE): Group? {
val query = (if (searchInfo.webDomain != null)
searchInfo.webDomain
else
searchInfo.applicationId)
?: return null
return mSearchHelper?.createVirtualGroupWithSearchResult(this, query, max)
fun createVirtualGroupFromSearchInfo(searchInfoString: String,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchInfoString, SearchParameters().apply {
searchInTitles = false
searchInUserNames = false
searchInPasswords = false
searchInUrls = true
searchInNotes = true
searchInOther = true
searchInUUIDs = false
searchInTags = false
ignoreCase = true
}, max)
}
@Throws(DatabaseOutputException::class)
@@ -477,7 +486,9 @@ class Database {
mDatabaseKDBX?.clearCache()
// In all cases, delete all the files in the temp dir
try {
FileUtils.cleanDirectory(filesDirectory)
filesDirectory?.let { directory ->
cleanDirectory(directory)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to clear the directory cache.", e)
}
@@ -488,6 +499,17 @@ class Database {
this.loaded = false
}
private fun cleanDirectory(directory: File) {
directory.listFiles()?.let { files ->
for (file in files) {
if (file.isDirectory) {
cleanDirectory(file)
}
file.delete()
}
}
}
fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
return mDatabaseKDB?.validatePasswordEncoding(password, containsKeyFile)
?: mDatabaseKDBX?.validatePasswordEncoding(password, containsKeyFile)

View File

@@ -41,7 +41,6 @@ class DeletedObject {
constructor()
@JvmOverloads
constructor(uuid: UUID, deletionTime: Date = Date()) {
this.uuid = uuid
this.mDeletionTime = deletionTime

View File

@@ -260,9 +260,9 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
return entriesInfo
}
fun getFilteredChildEntries(vararg filter: ChildFilter): List<Entry> {
val withoutMetaStream = filter.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED)
fun getFilteredChildEntries(filters: Array<ChildFilter>): List<Entry> {
val withoutMetaStream = filters.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filters.contains(ChildFilter.EXPIRED)
return groupKDB?.getChildEntries()?.filter {
(!withoutMetaStream || (withoutMetaStream && !it.isMetaStream))
@@ -278,8 +278,8 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
ArrayList()
}
fun getNumberOfChildEntries(vararg filter: ChildFilter): Int {
return getFilteredChildEntries(*filter).size
fun getNumberOfChildEntries(filters: Array<ChildFilter> = emptyArray()): Int {
return getFilteredChildEntries(filters).size
}
/**
@@ -290,8 +290,8 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
return getChildGroups() + getChildEntries()
}
fun getFilteredChildren(vararg filter: ChildFilter): List<Node> {
return getChildGroups() + getFilteredChildEntries(*filter)
fun getFilteredChildren(filters: Array<ChildFilter>): List<Node> {
return getChildGroups() + getFilteredChildEntries(filters)
}
override fun addChildGroup(group: Group) {

View File

@@ -19,7 +19,7 @@
package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.element.entry.EntryKDB
@@ -38,8 +38,6 @@ import kotlin.collections.ArrayList
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var numKeyEncRounds: Int = 0
var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
@@ -87,19 +85,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
override val passwordEncoding: String
get() = "ISO-8859-1"
override var numberKeyEncryptionRounds: Long
get() = numKeyEncRounds.toLong()
@Throws(NumberFormatException::class)
set(rounds) {
if (rounds > Integer.MAX_VALUE || rounds < Integer.MIN_VALUE) {
throw NumberFormatException()
}
numKeyEncRounds = rounds.toInt()
}
override var numberKeyEncryptionRounds = 300L
init {
algorithm = EncryptionAlgorithm.AESRijndael
numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS
}
/**
@@ -158,9 +147,9 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
val nos = NullOutputStream()
val dos = DigestOutputStream(nos, messageDigest)
val transformedMasterKey = transformMasterKey(masterSeed2, masterKey, numRounds)
// Encrypt the master key a few times to make brute-force key-search harder
dos.write(masterSeed)
dos.write(transformedMasterKey)
dos.write(AESKeyTransformerFactory.transformMasterKey(masterSeed2, masterKey, numRounds) ?: ByteArray(0))
finalKey = messageDigest.digest()
}
@@ -266,19 +255,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
const val BACKUP_FOLDER_TITLE = "Backup"
private const val BACKUP_FOLDER_UNDEFINED_ID = -1
private const val DEFAULT_ENCRYPTION_ROUNDS = 300
const val BUFFER_SIZE_BYTES = 3 * 128
/**
* Encrypt the master key a few times to make brute-force key-search harder
* @throws IOException
*/
@Throws(IOException::class)
private fun transformMasterKey(pKeySeed: ByteArray, pKey: ByteArray, rounds: Long): ByteArray {
val key = FinalKeyFactory.createFinalKey()
return key.transformMasterKey(pKeySeed, pKey, rounds)
}
}
}

View File

@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node
import org.w3c.dom.Text
@@ -67,7 +68,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary()
var kdbxVersion: Long = 0
var kdbxVersion = UnsignedInt(0)
var name = ""
var nameChanged = DateInstant()
// TODO change setting date
@@ -77,13 +78,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var defaultUserName = ""
var defaultUserNameChanged = DateInstant()
// TODO date
// TODO last change date
var keyLastChanged = DateInstant()
var keyChangeRecDays: Long = -1
var keyChangeForceDays: Long = 1
var isKeyChangeForceOnce = false
var maintenanceHistoryDays: Long = 365
var maintenanceHistoryDays = UnsignedInt(365)
var color = ""
/**
* Determine if RecycleBin is enable or not
@@ -219,7 +220,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!)
return numKeyEncRounds
}
@Throws(NumberFormatException::class)
set(rounds) {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
@@ -232,7 +232,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) {
kdfEngine.getMemoryUsage(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE.toLong()
} else KdfEngine.UNKNOWN_VALUE
}
set(memory) {
val kdfEngine = kdfEngine
@@ -240,7 +240,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
kdfEngine.setMemoryUsage(kdfParameters!!, memory)
}
var parallelism: Int
var parallelism: Long
get() {
val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) {

View File

@@ -20,15 +20,14 @@
package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException
import org.apache.commons.io.IOUtils
import java.io.*
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
@@ -104,10 +103,7 @@ abstract class DatabaseVersioned<
}
@Throws(IOException::class)
protected fun getPasswordKey(key: String?): ByteArray {
if (key == null)
throw IllegalArgumentException("Key cannot be empty.") // TODO
protected fun getPasswordKey(key: String): ByteArray {
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
@@ -130,7 +126,7 @@ abstract class DatabaseVersioned<
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyByteArrayOutputStream = ByteArrayOutputStream()
IOUtils.copy(keyInputStream, keyByteArrayOutputStream)
keyInputStream.copyTo(keyByteArrayOutputStream)
val keyData = keyByteArrayOutputStream.toByteArray()
val keyByteArrayInputStream = ByteArrayInputStream(keyData)

View File

@@ -23,6 +23,7 @@ import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedInt
import java.util.HashMap
@@ -46,7 +47,7 @@ class AutoType : Parcelable {
constructor(parcel: Parcel) {
this.enabled = parcel.readByte().toInt() != 0
this.obfuscationOptions = parcel.readLong()
this.obfuscationOptions = UnsignedInt(parcel.readInt())
this.defaultSequence = parcel.readString() ?: defaultSequence
this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel)
}
@@ -57,7 +58,7 @@ class AutoType : Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (enabled) 1 else 0).toByte())
dest.writeLong(obfuscationOptions)
dest.writeInt(obfuscationOptions.toInt())
dest.writeString(defaultSequence)
ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs)
}
@@ -71,7 +72,7 @@ class AutoType : Parcelable {
}
companion object {
private const val OBF_OPT_NONE: Long = 0
private val OBF_OPT_NONE = UnsignedInt(0)
@JvmField
val CREATOR: Parcelable.Creator<AutoType> = object : Parcelable.Creator<AutoType> {

View File

@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
@@ -104,7 +105,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
usageCount = parcel.readLong()
usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel)
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
@@ -122,7 +123,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount)
dest.writeLong(usageCount.toLong())
dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
@@ -243,7 +244,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
fields[STR_NOTES] = ProtectedString(protect, value)
}
override var usageCount: Long = 0
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
@@ -332,7 +333,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun touch(modified: Boolean, touchParents: Boolean) {
super.touch(modified, touchParents)
++usageCount
// TODO unsigned long
usageCount = UnsignedLong(usageCount.toLong() + 1)
}
companion object {

View File

@@ -22,8 +22,7 @@ package com.kunzisoft.keepass.database.element.entry
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler
import com.kunzisoft.keepass.database.search.SearchParametersKDBX
import com.kunzisoft.keepass.utils.StringUtil
import com.kunzisoft.keepass.database.search.SearchParameters
import java.util.*
class FieldReferencesEngine {
@@ -76,16 +75,16 @@ class FieldReferencesEngine {
for (i in 0..19) {
text = fillRefsUsingCache(text, contextV4)
val start = StringUtil.indexOfIgnoreCase(text, STR_REF_START, offset, Locale.ENGLISH)
val start = text.indexOf(STR_REF_START, offset, true)
if (start < 0) {
break
}
val end = StringUtil.indexOfIgnoreCase(text, STR_REF_END, start + 1, Locale.ENGLISH)
val end = text.indexOf(STR_REF_END, start + 1, true)
if (end <= start) {
break
}
val fullRef = text.substring(start, end - start + 1)
val fullRef = text.substring(start, end + 1)
val result = findRefTarget(fullRef, contextV4)
if (result != null) {
@@ -128,7 +127,7 @@ class FieldReferencesEngine {
return null
}
val ref = fullRef.substring(STR_REF_START.length, fullRef.length - STR_REF_START.length - STR_REF_END.length)
val ref = fullRef.substring(STR_REF_START.length, fullRef.length - STR_REF_END.length)
if (ref.length <= 4) {
return null
}
@@ -142,24 +141,23 @@ class FieldReferencesEngine {
val scan = Character.toUpperCase(ref[2])
val wanted = Character.toUpperCase(ref[0])
val searchParametersV4 = SearchParametersKDBX()
searchParametersV4.setupNone()
val searchParameters = SearchParameters()
searchParameters.setupNone()
searchParametersV4.searchString = ref.substring(4)
searchParameters.searchString = ref.substring(4)
when (scan) {
'T' -> searchParametersV4.searchInTitles = true
'U' -> searchParametersV4.searchInUserNames = true
'A' -> searchParametersV4.searchInUrls = true
'P' -> searchParametersV4.searchInPasswords = true
'N' -> searchParametersV4.searchInNotes = true
'I' -> searchParametersV4.searchInUUIDs = true
'O' -> searchParametersV4.searchInOther = true
'T' -> searchParameters.searchInTitles = true
'U' -> searchParameters.searchInUserNames = true
'A' -> searchParameters.searchInUrls = true
'P' -> searchParameters.searchInPasswords = true
'N' -> searchParameters.searchInNotes = true
'I' -> searchParameters.searchInUUIDs = true
'O' -> searchParameters.searchInOther = true
else -> return null
}
val list = ArrayList<EntryKDBX>()
// TODO type parameter
searchEntries(contextV4.databaseV4!!.rootGroup, searchParametersV4, list)
searchEntries(contextV4.databaseV4?.rootGroup, searchParameters, list)
return if (list.size > 0) {
TargetResult(list[0], wanted)
@@ -186,22 +184,22 @@ class FieldReferencesEngine {
private fun fillRefsUsingCache(text: String, sprContextV4: SprContextV4): String {
var newText = text
for ((key, value) in sprContextV4.refsCache) {
newText = StringUtil.replaceAllIgnoresCase(text, key, value, Locale.ENGLISH)
newText = text.replace(key, value, true)
}
return newText
}
private fun searchEntries(root: GroupKDBX?, searchParametersV4: SearchParametersKDBX?, listStorage: MutableList<EntryKDBX>?) {
if (searchParametersV4 == null) {
private fun searchEntries(root: GroupKDBX?, searchParameters: SearchParameters?, listStorage: MutableList<EntryKDBX>?) {
if (searchParameters == null) {
return
}
if (listStorage == null) {
return
}
val terms = StringUtil.splitStringTerms(searchParametersV4.searchString)
if (terms.size <= 1 || searchParametersV4.regularExpression) {
root!!.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, listStorage), null)
val terms = splitStringTerms(searchParameters.searchString)
if (terms.size <= 1 || searchParameters.regularExpression) {
root!!.doForEachChild(EntryKDBXSearchHandler(searchParameters, listStorage), null)
return
}
@@ -209,41 +207,76 @@ class FieldReferencesEngine {
val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length }
Collections.sort(terms, stringLengthComparator)
val fullSearch = searchParametersV4.searchString
val fullSearch = searchParameters.searchString
var childEntries: List<EntryKDBX>? = root!!.getChildEntries()
for (i in terms.indices) {
val pgNew = ArrayList<EntryKDBX>()
searchParametersV4.searchString = terms[i]
searchParameters.searchString = terms[i]
var negate = false
if (searchParametersV4.searchString.startsWith("-")) {
searchParametersV4.searchString = searchParametersV4.searchString.substring(1)
negate = searchParametersV4.searchString.isNotEmpty()
if (searchParameters.searchString.startsWith("-")) {
searchParameters.searchString = searchParameters.searchString.substring(1)
negate = searchParameters.searchString.isNotEmpty()
}
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, pgNew), null)) {
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParameters, pgNew), null)) {
childEntries = null
break
}
val complement = ArrayList<EntryKDBX>()
if (negate) {
childEntries = if (negate) {
val complement = ArrayList<EntryKDBX>()
for (entry in childEntries!!) {
if (!pgNew.contains(entry)) {
complement.add(entry)
}
}
childEntries = complement
complement
} else {
childEntries = pgNew
pgNew
}
}
if (childEntries != null) {
listStorage.addAll(childEntries)
}
searchParametersV4.searchString = fullSearch
searchParameters.searchString = fullSearch
}
/**
* Create a list of String by split text when ' ', '\t', '\r' or '\n' is found
*/
private fun splitStringTerms(text: String?): List<String> {
val list = ArrayList<String>()
if (text == null) {
return list
}
val stringBuilder = StringBuilder()
var quoted = false
for (element in text) {
if ((element == ' ' || element == '\t' || element == '\r' || element == '\n') && !quoted) {
val len = stringBuilder.length
when {
len > 0 -> {
list.add(stringBuilder.toString())
stringBuilder.delete(0, len)
}
element == '\"' -> quoted = !quoted
else -> stringBuilder.append(element)
}
}
}
if (stringBuilder.isNotEmpty()) {
list.add(stringBuilder.toString())
}
return list
}
companion object {

View File

@@ -32,14 +32,14 @@ import java.util.*
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
var level = 0 // short
/** Used by KeePass internally, don't use */
var flags: Int = 0
// Used by KeePass internally, don't use
var groupFlags = 0
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
level = parcel.readInt()
flags = parcel.readInt()
groupFlags = parcel.readInt()
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -53,13 +53,13 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeInt(level)
dest.writeInt(flags)
dest.writeInt(groupFlags)
}
fun updateWith(source: GroupKDB) {
super.updateWith(source)
level = source.level
flags = source.flags
groupFlags = source.groupFlags
}
override val type: Type

View File

@@ -31,6 +31,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.HashMap
import java.util.UUID
@@ -77,9 +78,9 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
usageCount = parcel.readLong()
usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
// TODO customData = ParcelableUtil.readStringParcelableMap(in);
// TODO customData = ParcelableUtil.readStringParcelableMap(parcel);
notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
@@ -101,7 +102,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount)
dest.writeLong(usageCount.toLong())
dest.writeParcelable(locationChanged, flags)
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
dest.writeString(notes)
@@ -130,7 +131,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
lastTopVisibleEntry = source.lastTopVisibleEntry
}
override var usageCount: Long = 0
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()

View File

@@ -49,7 +49,7 @@ class IconImageCustom : IconImage {
constructor(parcel: Parcel) {
uuid = parcel.readSerializable() as UUID
// TODO Take too much memories
// in.readByteArray(imageData);
// parcel.readByteArray(imageData);
}
override fun writeToParcel(dest: Parcel, flags: Int) {

View File

@@ -31,7 +31,6 @@ class NodeIdInt : NodeId<Int> {
constructor(source: NodeIdInt) : this(source.id)
@JvmOverloads
constructor(groupId: Int = Random().nextInt()) : super() {
this.id = groupId
}

View File

@@ -30,7 +30,6 @@ class NodeIdUUID : NodeId<UUID> {
constructor(source: NodeIdUUID) : this(source.id)
@JvmOverloads
constructor(uuid: UUID = UUID.randomUUID()) : super() {
this.id = uuid
}

View File

@@ -20,10 +20,11 @@
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.utils.UnsignedLong
interface NodeKDBXInterface : NodeTimeInterface {
var usageCount: Long
var usageCount: UnsignedLong
var locationChanged: DateInstant

View File

@@ -33,7 +33,6 @@ class ProtectedString : Parcelable {
this.stringValue = toCopy.stringValue
}
@JvmOverloads
constructor(enableProtection: Boolean = false, string: String = "") {
this.isProtected = enableProtection
this.stringValue = string

View File

@@ -19,6 +19,8 @@
*/
package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.utils.UnsignedInt
abstract class DatabaseHeader {
/**
@@ -32,7 +34,7 @@ abstract class DatabaseHeader {
var encryptionIV = ByteArray(16)
companion object {
const val PWM_DBSIG_1 = -0x655d26fd
val PWM_DBSIG_1 = UnsignedInt(-0x655d26fd)
}
}

View File

@@ -21,9 +21,9 @@
package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.stream.bytes4ToInt
import com.kunzisoft.keepass.stream.readBytesLength
import com.kunzisoft.keepass.stream.readBytes4ToInt
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.InputStream
@@ -34,15 +34,15 @@ class DatabaseHeaderKDB : DatabaseHeader() {
*/
var transformSeed = ByteArray(32)
var signature1: Int = 0 // = PWM_DBSIG_1
var signature2: Int = 0 // = DBSIG_2
var flags: Int = 0
var version: Int = 0
var signature1 = UnsignedInt(0) // = PWM_DBSIG_1
var signature2 = UnsignedInt(0) // = DBSIG_2
var flags= UnsignedInt(0)
var version= UnsignedInt(0)
/** Number of groups in the database */
var numGroups: Int = 0
var numGroups = UnsignedInt(0)
/** Number of entries in the database */
var numEntries: Int = 0
var numEntries = UnsignedInt(0)
/**
* SHA-256 hash of the database, used for integrity check
@@ -50,28 +50,24 @@ class DatabaseHeaderKDB : DatabaseHeader() {
var contentsHash = ByteArray(32)
// As UInt
var numKeyEncRounds: Int = 0
var numKeyEncRounds = UnsignedInt(0)
/**
* Parse given buf, as read from file.
*/
@Throws(IOException::class)
fun loadFromFile(inputStream: InputStream) {
signature1 = inputStream.readBytes4ToInt() // 4 bytes
signature2 = inputStream.readBytes4ToInt() // 4 bytes
flags = inputStream.readBytes4ToInt() // 4 bytes
version = inputStream.readBytes4ToInt() // 4 bytes
signature1 = inputStream.readBytes4ToUInt() // 4 bytes
signature2 = inputStream.readBytes4ToUInt() // 4 bytes
flags = inputStream.readBytes4ToUInt() // 4 bytes
version = inputStream.readBytes4ToUInt() // 4 bytes
masterSeed = inputStream.readBytesLength(16) // 16 bytes
encryptionIV = inputStream.readBytesLength(16) // 16 bytes
numGroups = inputStream.readBytes4ToInt() // 4 bytes
numEntries = inputStream.readBytes4ToInt() // 4 bytes
numGroups = inputStream.readBytes4ToUInt() // 4 bytes
numEntries = inputStream.readBytes4ToUInt() // 4 bytes
contentsHash = inputStream.readBytesLength(32) // 32 bytes
transformSeed = inputStream.readBytesLength(32) // 32 bytes
numKeyEncRounds = inputStream.readBytes4ToInt()
if (numKeyEncRounds < 0) {
// TODO: Really treat this like an unsigned integer
throw IOException("Does not support more than " + Integer.MAX_VALUE + " rounds.")
}
numKeyEncRounds = inputStream.readBytes4ToUInt()
}
init {
@@ -88,24 +84,24 @@ class DatabaseHeaderKDB : DatabaseHeader() {
companion object {
// DB sig from KeePass 1.03
const val DBSIG_2 = -0x4ab4049b
val DBSIG_2 = UnsignedInt(-0x4ab4049b)
// DB sig from KeePass 1.03
const val DBVER_DW = 0x00030003
val DBVER_DW = UnsignedInt(0x00030003)
const val FLAG_SHA2 = 1
const val FLAG_RIJNDAEL = 2
const val FLAG_ARCFOUR = 4
const val FLAG_TWOFISH = 8
val FLAG_SHA2 = UnsignedInt(1)
val FLAG_RIJNDAEL = UnsignedInt(2)
val FLAG_ARCFOUR = UnsignedInt(4)
val FLAG_TWOFISH = UnsignedInt(8)
/** Size of byte buffer needed to hold this struct. */
const val BUF_SIZE = 124
fun matchesHeader(sig1: Int, sig2: Int): Boolean {
return sig1 == PWM_DBSIG_1 && sig2 == DBSIG_2
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1.toInt() == PWM_DBSIG_1.toInt() && sig2.toInt() == DBSIG_2.toInt()
}
fun compatibleHeaders(one: Int, two: Int): Boolean {
return one and -0x100 == two and -0x100
fun compatibleHeaders(one: UnsignedInt, two: UnsignedInt): Boolean {
return one.toInt() and -0x100 == two.toInt() and -0x100
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -31,6 +31,8 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
@@ -45,14 +47,16 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
var innerRandomStreamKey: ByteArray = ByteArray(32)
var streamStartBytes: ByteArray = ByteArray(32)
var innerRandomStream: CrsAlgorithm? = null
var version: Long = 0
var version: UnsignedInt = UnsignedInt(0)
// version < FILE_VERSION_32_4)
var transformSeed: ByteArray?
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
private set(seed) {
assignAesKdfEngineIfNotExists()
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, seed)
seed?.let {
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, it)
}
}
object PwDbHeaderV4Fields {
@@ -103,7 +107,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
}
}
private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): Long {
private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): UnsignedInt {
// https://keepass.info/help/kb/kdbx_4.html
// Return v4 if AES is not use
@@ -147,8 +151,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
val sig1 = littleEndianDataInputStream.readInt()
val sig2 = littleEndianDataInputStream.readInt()
val sig1 = littleEndianDataInputStream.readUInt()
val sig2 = littleEndianDataInputStream.readUInt()
if (!matchesHeader(sig1, sig2)) {
throw VersionDatabaseException()
@@ -172,10 +176,10 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean {
val fieldID = dis.read().toByte()
val fieldSize: Int = if (version < FILE_VERSION_32_4) {
val fieldSize: Int = if (version.toLong() < FILE_VERSION_32_4.toLong()) {
dis.readUShort()
} else {
dis.readInt()
dis.readUInt().toInt()
}
var fieldData: ByteArray? = null
@@ -198,20 +202,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
PwDbHeaderV4Fields.TransformSeed -> if (version < FILE_VERSION_32_4)
PwDbHeaderV4Fields.TransformSeed -> if (version.toLong() < FILE_VERSION_32_4.toLong())
transformSeed = fieldData
PwDbHeaderV4Fields.TransformRounds -> if (version < FILE_VERSION_32_4)
PwDbHeaderV4Fields.TransformRounds -> if (version.toLong() < FILE_VERSION_32_4.toLong())
setTransformRound(fieldData)
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version < FILE_VERSION_32_4)
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.toLong() < FILE_VERSION_32_4.toLong())
innerRandomStreamKey = fieldData
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version < FILE_VERSION_32_4)
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.toLong() < FILE_VERSION_32_4.toLong())
setRandomStreamID(fieldData)
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
@@ -256,8 +260,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid compression flags.")
}
val flag = bytes4ToInt(pbFlags)
if (flag < 0 || flag >= CompressionAlgorithm.values().size) {
val flag = bytes4ToUInt(pbFlags)
if (flag.toLong() < 0 || flag.toLong() >= CompressionAlgorithm.values().size) {
throw IOException("Unrecognized compression flag.")
}
@@ -272,8 +276,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid stream id.")
}
val id = bytes4ToInt(streamID)
if (id < 0 || id >= CrsAlgorithm.values().size) {
val id = bytes4ToUInt(streamID)
if (id.toInt() < 0 || id.toInt() >= CrsAlgorithm.values().size) {
throw IOException("Invalid stream id.")
}
@@ -287,43 +291,42 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
* @param version Database version
* @return true if it's a supported version
*/
private fun validVersion(version: Long): Boolean {
return version and FILE_VERSION_CRITICAL_MASK <= FILE_VERSION_32_4 and FILE_VERSION_CRITICAL_MASK
private fun validVersion(version: UnsignedInt): Boolean {
return version.toInt() and FILE_VERSION_CRITICAL_MASK.toInt() <=
FILE_VERSION_32_4.toInt() and FILE_VERSION_CRITICAL_MASK.toInt()
}
companion object {
var ULONG_MAX_VALUE: Long = -1
val DBSIG_PRE2 = UnsignedInt(-0x4ab4049a)
val DBSIG_2 = UnsignedInt(-0x4ab40499)
const val DBSIG_PRE2 = -0x4ab4049a
const val DBSIG_2 = -0x4ab40499
private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000)
val FILE_VERSION_32_3 = UnsignedInt(0x00030001)
val FILE_VERSION_32_4 = UnsignedInt(0x00040000)
private const val FILE_VERSION_CRITICAL_MASK: Long = -0x10000
const val FILE_VERSION_32_3: Long = 0x00030001
const val FILE_VERSION_32_4: Long = 0x00040000
fun getCompressionFromFlag(flag: Int): CompressionAlgorithm? {
return when (flag) {
fun getCompressionFromFlag(flag: UnsignedInt): CompressionAlgorithm? {
return when (flag.toInt()) {
0 -> CompressionAlgorithm.None
1 -> CompressionAlgorithm.GZip
else -> null
}
}
fun getFlagFromCompression(compression: CompressionAlgorithm): Int {
fun getFlagFromCompression(compression: CompressionAlgorithm): UnsignedInt {
return when (compression) {
CompressionAlgorithm.GZip -> 1
else -> 0
CompressionAlgorithm.GZip -> UnsignedInt(1)
else -> UnsignedInt(0)
}
}
fun matchesHeader(sig1: Int, sig2: Int): Boolean {
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
}
@Throws(IOException::class)
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
val blockKey = HmacBlockStream.getHmacKey64(key, ULONG_MAX_VALUE)
val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.MAX_VALUE)
val hmac: Mac
try {

View File

@@ -90,10 +90,10 @@ class DatabaseInputKDB(cacheDirectory: File,
// Select algorithm
when {
header.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> {
header.flags.toInt() and DatabaseHeaderKDB.FLAG_RIJNDAEL.toInt() != 0 -> {
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael
}
header.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> {
header.flags.toInt() and DatabaseHeaderKDB.FLAG_TWOFISH.toInt() != 0 -> {
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish
}
else -> throw InvalidAlgorithmDatabaseException()
@@ -150,26 +150,6 @@ class DatabaseInputKDB(cacheDirectory: File,
)
)
/* TODO checksum
// Add a mark to the content start
if (!cipherInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
cipherInputStream.mark(cipherInputStream.available() +1)
// Consume all data to get the digest
var numberRead = 0
while (numberRead > -1) {
numberRead = cipherInputStream.read(ByteArray(1024))
}
// Check sum
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
throw InvalidCredentialsDatabaseException()
}
// Back to the content start
cipherInputStream.reset()
*/
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
val newRoot = mDatabaseToOpen.createGroup()
newRoot.level = -1
@@ -180,8 +160,8 @@ class DatabaseInputKDB(cacheDirectory: File,
var newEntry: EntryKDB? = null
var currentGroupNumber = 0
var currentEntryNumber = 0
while (currentGroupNumber < header.numGroups
|| currentEntryNumber < header.numEntries) {
while (currentGroupNumber < header.numGroups.toLong()
|| currentEntryNumber < header.numEntries.toLong()) {
val fieldType = cipherInputStream.readBytes2ToUShort()
val fieldSize = cipherInputStream.readBytes4ToUInt().toInt()
@@ -195,7 +175,7 @@ class DatabaseInputKDB(cacheDirectory: File,
when (fieldSize) {
4 -> {
newGroup = mDatabaseToOpen.createGroup().apply {
setGroupId(cipherInputStream.readBytes4ToInt())
setGroupId(cipherInputStream.readBytes4ToUInt().toInt())
}
}
16 -> {
@@ -214,7 +194,7 @@ class DatabaseInputKDB(cacheDirectory: File,
} ?:
newEntry?.let { entry ->
val groupKDB = mDatabaseToOpen.createGroup()
groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToInt())
groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToUInt().toInt())
entry.parent = groupKDB
}
}
@@ -223,7 +203,7 @@ class DatabaseInputKDB(cacheDirectory: File,
group.creationTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
var iconId = cipherInputStream.readBytes4ToInt()
var iconId = cipherInputStream.readBytes4ToUInt().toInt()
// Clean up after bug that set icon ids to -1
if (iconId == -1) {
iconId = 0
@@ -257,7 +237,7 @@ class DatabaseInputKDB(cacheDirectory: File,
}
0x0007 -> {
newGroup?.let { group ->
group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToInt())
group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToUInt().toInt())
} ?:
newEntry?.let { entry ->
entry.password = cipherInputStream.readBytesToString(fieldSize,false)
@@ -273,7 +253,7 @@ class DatabaseInputKDB(cacheDirectory: File,
}
0x0009 -> {
newGroup?.let { group ->
group.flags = cipherInputStream.readBytes4ToInt()
group.groupFlags = cipherInputStream.readBytes4ToUInt().toInt()
} ?:
newEntry?.let { entry ->
entry.creationTime = cipherInputStream.readBytes5ToDate()

View File

@@ -43,7 +43,9 @@ import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import org.spongycastle.crypto.StreamCipher
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import org.bouncycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory
@@ -130,7 +132,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
}
val isPlain: InputStream
if (mDatabase.kdbxVersion < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (mDatabase.kdbxVersion.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
val decrypted = attachCipherStream(databaseInputStream, cipher)
val dataDecrypted = LittleEndianDataInputStream(decrypted)
@@ -178,7 +180,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
else -> isPlain
}
if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (mDatabase.kdbxVersion.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
loadInnerHeader(inputStreamXml, header)
}
@@ -226,19 +228,24 @@ class DatabaseInputKDBX(cacheDirectory: File,
header: DatabaseHeaderKDBX): Boolean {
val fieldId = dataInputStream.read().toByte()
val size = dataInputStream.readInt()
val size = dataInputStream.readUInt().toInt()
if (size < 0) throw IOException("Corrupted file")
var data = ByteArray(0)
if (size > 0) {
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
data = dataInputStream.readBytes(size)
}
var result = true
when (fieldId) {
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> {
return false
result = false
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> {
val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0)
header.setRandomStreamID(data)
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> {
val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0)
header.innerRandomStreamKey = data
}
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
@@ -255,12 +262,9 @@ class DatabaseInputKDBX(cacheDirectory: File,
val protectedBinary = BinaryAttachment(file, protectedFlag)
mDatabase.binaryPool.add(protectedBinary)
}
else -> {
return false
}
}
return true
return result
}
private enum class KdbContext {
@@ -488,7 +492,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
} else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) {
ctxGroup?.notes = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt())
ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toInt())
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
@@ -542,7 +546,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt())
ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toInt())
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) {
@@ -598,7 +602,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp)
name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp)
name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false)
name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, 0)
name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, UnsignedLong(0))
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp)
else -> readUnknown(xpp)
}
@@ -621,7 +625,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
KdbContext.EntryAutoType -> if (name.equals(DatabaseKDBXXML.ElemAutoTypeEnabled, ignoreCase = true)) {
ctxEntry?.autoType?.enabled = readBool(xpp, true)
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeObfuscation, ignoreCase = true)) {
ctxEntry?.autoType?.obfuscationOptions = readUInt(xpp, 0)
ctxEntry?.autoType?.obfuscationOptions = readUInt(xpp, UnsignedInt(0))
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, ignoreCase = true)) {
ctxEntry?.autoType?.defaultSequence = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) {
@@ -815,7 +819,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
val sDate = readString(xpp)
var utcDate: Date? = null
if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (mDatabase.kdbxVersion.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
var buf = Base64.decode(sDate, BASE_64_FLAG)
if (buf.size != 8) {
val buf8 = ByteArray(8)
@@ -887,48 +891,39 @@ class DatabaseInputKDBX(cacheDirectory: File,
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readInt(xpp: XmlPullParser, def: Int): Int {
val str = readString(xpp)
private fun readInt(xpp: XmlPullParser, default: Int): Int {
return try {
Integer.parseInt(str)
} catch (e: NumberFormatException) {
def
readString(xpp).toInt()
} catch (e: Exception) {
default
}
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readUInt(xpp: XmlPullParser, uDefault: Long): Long {
val u: Long = readULong(xpp, uDefault)
if (u < 0 || u > MAX_UINT) {
throw NumberFormatException("Outside of the uint size")
}
return u
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readLong(xpp: XmlPullParser, def: Long): Long {
val str = readString(xpp)
private fun readUInt(xpp: XmlPullParser, default: UnsignedInt): UnsignedInt {
return try {
java.lang.Long.parseLong(str)
} catch (e: NumberFormatException) {
def
UnsignedInt(readString(xpp).toInt())
} catch (e: Exception) {
default
}
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readULong(xpp: XmlPullParser, uDefault: Long): Long {
var u = readLong(xpp, uDefault)
if (u < 0) {
u = uDefault
private fun readLong(xpp: XmlPullParser, default: Long): Long {
return try {
readString(xpp).toLong()
} catch (e: Exception) {
default
}
}
return u
@Throws(IOException::class, XmlPullParserException::class)
private fun readULong(xpp: XmlPullParser, default: UnsignedLong): UnsignedLong {
return try {
UnsignedLong(readString(xpp).toLong())
} catch (e: Exception) {
default
}
}
@Throws(XmlPullParserException::class, IOException::class)
@@ -1050,7 +1045,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
companion object {
private const val DEFAULT_HISTORY_DAYS: Long = 365
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
@Throws(XmlPullParserException::class)
private fun createPullParser(readerStream: InputStream): XmlPullParser {
@@ -1062,8 +1057,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
return xpp
}
private const val MAX_UINT = 4294967296L // 2^32
}
}

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.intTo4Bytes
import com.kunzisoft.keepass.stream.uIntTo4Bytes
import java.io.IOException
import java.io.OutputStream
@@ -29,14 +29,14 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
@Throws(IOException::class)
fun outputStart() {
mOutputStream.write(intTo4Bytes(mHeader.signature1))
mOutputStream.write(intTo4Bytes(mHeader.signature2))
mOutputStream.write(intTo4Bytes(mHeader.flags))
mOutputStream.write(intTo4Bytes(mHeader.version))
mOutputStream.write(uIntTo4Bytes(mHeader.signature1))
mOutputStream.write(uIntTo4Bytes(mHeader.signature2))
mOutputStream.write(uIntTo4Bytes(mHeader.flags))
mOutputStream.write(uIntTo4Bytes(mHeader.version))
mOutputStream.write(mHeader.masterSeed)
mOutputStream.write(mHeader.encryptionIV)
mOutputStream.write(intTo4Bytes(mHeader.numGroups))
mOutputStream.write(intTo4Bytes(mHeader.numEntries))
mOutputStream.write(uIntTo4Bytes(mHeader.numGroups))
mOutputStream.write(uIntTo4Bytes(mHeader.numEntries))
}
@Throws(IOException::class)
@@ -47,7 +47,7 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
@Throws(IOException::class)
fun outputEnd() {
mOutputStream.write(mHeader.transformSeed)
mOutputStream.write(intTo4Bytes(mHeader.numKeyEncRounds))
mOutputStream.write(uIntTo4Bytes(mHeader.numKeyEncRounds))
}
@Throws(IOException::class)

View File

@@ -24,8 +24,8 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.VariantDictionary
import java.io.ByteArrayOutputStream
import java.io.IOException
@@ -66,7 +66,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, ULONG_MAX_VALUE), "HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.MAX_VALUE), "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException(e)
@@ -82,15 +82,15 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
fun output() {
los.writeUInt(DatabaseHeader.PWM_DBSIG_1.toLong())
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2.toLong())
los.writeUInt(DatabaseHeader.PWM_DBSIG_1)
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2)
los.writeUInt(header.version)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, intTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else {
@@ -101,10 +101,10 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
}
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, intTo4Bytes(header.innerRandomStream!!.id))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
}
if (databaseKDBX.containsPublicCustomData()) {
@@ -136,7 +136,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
private fun writeHeaderFieldSize(size: Int) {
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
los.writeUShort(size)
} else {
los.writeInt(size)

View File

@@ -40,7 +40,7 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
dataOutputStream.writeInt(4)
if (header.innerRandomStream == null)
throw IOException("Can't write innerRandomStream")
dataOutputStream.writeInt(header.innerRandomStream!!.id)
dataOutputStream.writeInt(header.innerRandomStream!!.id.toInt())
val streamKeySize = header.innerRandomStreamKey.size
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt())

View File

@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
@@ -117,18 +118,18 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
when {
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
header.flags = header.flags or DatabaseHeaderKDB.FLAG_RIJNDAEL
header.flags = UnsignedInt(header.flags.toInt() or DatabaseHeaderKDB.FLAG_RIJNDAEL.toInt())
}
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
header.flags = header.flags or DatabaseHeaderKDB.FLAG_TWOFISH
header.flags = UnsignedInt(header.flags.toInt() or DatabaseHeaderKDB.FLAG_TWOFISH.toInt())
}
else -> throw DatabaseOutputException("Unsupported algorithm.")
}
header.version = DatabaseHeaderKDB.DBVER_DW
header.numGroups = mDatabaseKDB.numberOfGroups()
header.numEntries = mDatabaseKDB.numberOfEntries()
header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt() // TODO Signed Long - Unsigned Int
header.numGroups = UnsignedInt(mDatabaseKDB.numberOfGroups())
header.numEntries = UnsignedInt(mDatabaseKDB.numberOfEntries())
header.numKeyEncRounds = UnsignedInt.fromLong(mDatabaseKDB.numberKeyEncryptionRounds)
setIVs(header)
@@ -279,6 +280,5 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
if (data != null) {
los.write(data)
}
}
}

View File

@@ -47,8 +47,8 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.*
import org.bouncycastle.crypto.StreamCipher
import org.joda.time.DateTime
import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlSerializer
import java.io.IOException
import java.io.OutputStream
@@ -85,7 +85,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
header = outputHeader(mOS)
val osPlain: OutputStream
osPlain = if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
osPlain = if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
val cos = attachStreamEncryptor(header!!, mOS)
cos.write(header!!.streamStartBytes)
@@ -105,7 +105,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
else -> osPlain
}
if (header!!.version >= DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (header!!.version.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml)
ihOut.output()
}
@@ -209,7 +209,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeObject(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged.date)
writeObject(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true)
writeObject(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged.date)
writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays)
writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays.toLong())
writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date)
writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
@@ -230,7 +230,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta
if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4)
if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong())
writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData)
@@ -274,7 +274,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
Log.e(TAG, "Unable to retrieve header", unknownKDF)
}
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
header.innerRandomStream = CrsAlgorithm.Salsa20
header.innerRandomStreamKey = ByteArray(32)
} else {
@@ -288,7 +288,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
throw DatabaseOutputException("Invalid random cipher")
}
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
random.nextBytes(header.streamStartBytes)
}
@@ -385,7 +385,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Date) {
if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
} else {
val dt = DateTime(value)
@@ -489,7 +489,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemAutoType)
writeObject(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled)
writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions)
writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions.toLong())
if (autoType.defaultSequence.isNotEmpty()) {
writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
@@ -629,7 +629,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeObject(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime.date)
writeObject(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime.date)
writeObject(DatabaseKDBXXML.ElemExpires, node.expires)
writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount)
writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount.toLong())
writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date)
xml.endTag(null, DatabaseKDBXXML.ElemTimes)

View File

@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.file.output.GroupOutputKDB.Companion.GROUPID_FIELD_SIZE
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.OutputStream
import java.nio.charset.Charset
@@ -54,12 +55,12 @@ class EntryOutputKDB
// Group ID
mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mEntry.parent!!.id))
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.parent!!.id)))
// Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mEntry.icon.iconId))
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.icon.iconId)))
// Title
//byte[] title = mEntry.title.getBytes("UTF-8");
@@ -101,14 +102,9 @@ class EntryOutputKDB
// Binary
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.binaryData
val binaryDataLength = binaryData?.length() ?: 0
val binaryDataLengthRightSize = if (binaryDataLength <= Int.MAX_VALUE) {
binaryDataLength.toInt()
} else {
0 // TODO if length > UInt.maxvalue show exception
}
val binaryDataLength = binaryData?.length() ?: 0L
// Write data length
mOutputStream.write(intTo4Bytes(binaryDataLengthRightSize))
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromLong(binaryDataLength)))
// Write data
if (binaryDataLength > 0) {
binaryData?.getInputDataStream().use { inputStream ->
@@ -140,7 +136,7 @@ class EntryOutputKDB
private fun writePassword(str: String, os: OutputStream): Int {
val initial = str.toByteArray(Charset.forName("UTF-8"))
val length = initial.size + 1
os.write(intTo4Bytes(length))
os.write(uIntTo4Bytes(UnsignedInt(length)))
os.write(initial)
os.write(0x00)
return length
@@ -164,13 +160,13 @@ class EntryOutputKDB
val BINARY_DATA_FIELD_TYPE:ByteArray = uShortTo2Bytes(14)
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val LONG_FOUR:ByteArray = intTo4Bytes(4)
val UUID_FIELD_SIZE:ByteArray = intTo4Bytes(16)
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
val LONG_FOUR:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val UUID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(16))
val DATE_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(5))
val IMAGEID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val LEVEL_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val FLAGS_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val ZERO_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(0))
val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)
}
}

View File

@@ -21,9 +21,10 @@ package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.stream.dateTo5Bytes
import com.kunzisoft.keepass.stream.intTo4Bytes
import com.kunzisoft.keepass.stream.uIntTo4Bytes
import com.kunzisoft.keepass.stream.uShortTo2Bytes
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.OutputStream
@@ -39,7 +40,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Group ID
mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mGroup.id))
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.id)))
// Name
mOutputStream.write(NAME_FIELD_TYPE)
@@ -68,7 +69,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mGroup.icon.iconId))
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.icon.iconId)))
// Level
mOutputStream.write(LEVEL_FIELD_TYPE)
@@ -78,7 +79,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Flags
mOutputStream.write(FLAGS_FIELD_TYPE)
mOutputStream.write(FLAGS_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mGroup.flags))
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.groupFlags)))
// End
mOutputStream.write(END_FIELD_TYPE)
@@ -98,11 +99,11 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
val FLAGS_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val GROUPID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(2)
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
val GROUPID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val DATE_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(5))
val IMAGEID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val LEVEL_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(2))
val FLAGS_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
val ZERO_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(0))
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -22,11 +22,10 @@ package com.kunzisoft.keepass.database.search
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
import com.kunzisoft.keepass.utils.StringUtil
import java.util.Locale
class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParametersKDBX, private val mListStorage: MutableList<EntryKDBX>) : NodeHandler<EntryKDBX>() {
class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters,
private val mListStorage: MutableList<EntryKDBX>)
: NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean {
@@ -35,32 +34,17 @@ class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters
return true
}
var term = mSearchParametersKDBX.searchString
if (mSearchParametersKDBX.ignoreCase) {
term = term.toLowerCase()
}
if (searchStrings(node, term)) {
if (searchStrings(node)) {
mListStorage.add(node)
return true
}
if (mSearchParametersKDBX.searchInGroupNames) {
val parent = node.parent
if (parent != null) {
var groupName = parent.title
if (mSearchParametersKDBX.ignoreCase) {
groupName = groupName.toLowerCase()
}
if (groupName.contains(term)) {
mListStorage.add(node)
return true
}
}
if (searchInGroupNames(node)) {
mListStorage.add(node)
return true
}
if (searchID(node)) {
if (searchInUUID(node)) {
mListStorage.add(node)
return true
}
@@ -68,25 +52,33 @@ class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters
return true
}
private fun searchID(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInUUIDs) {
val hex = UuidUtil.toHexString(entry.id)
return StringUtil.indexOfIgnoreCase(hex, mSearchParametersKDBX.searchString, Locale.ENGLISH) >= 0
private fun searchInGroupNames(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInGroupNames) {
val parent = entry.parent
if (parent != null) {
return parent.title
.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)
}
}
return false
}
private fun searchStrings(entry: EntryKDBX, term: String): Boolean {
private fun searchInUUID(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInUUIDs) {
return UuidUtil.toHexString(entry.id)
.contains(mSearchParametersKDBX.searchString, true)
}
return false
}
private fun searchStrings(entry: EntryKDBX): Boolean {
val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX)
while (iterator.hasNext()) {
var str = iterator.next()
if (str.isNotEmpty()) {
if (mSearchParametersKDBX.ignoreCase) {
str = str.toLowerCase()
}
if (str.contains(term)) {
val stringValue = iterator.next()
if (stringValue.isNotEmpty()) {
if (stringValue.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)) {
return true
}
}

View File

@@ -19,23 +19,65 @@
*/
package com.kunzisoft.keepass.database.search
import android.content.Context
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIterator
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.model.getSearchString
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
class SearchHelper(private val isOmitBackup: Boolean) {
companion object {
const val MAX_SEARCH_ENTRY = 6
/**
* Utility method to perform actions if item is found or not after an auto search in [database]
*/
fun checkAutoSearchInfo(context: Context,
database: Database,
searchInfo: SearchInfo?,
onItemsFound: (items: List<EntryInfo>) -> Unit,
onItemNotFound: () -> Unit,
onDatabaseClosed: () -> Unit) {
if (database.loaded && TimeoutHelper.checkTime(context)) {
var searchWithoutUI = false
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
&& searchInfo != null
&& !searchInfo.containsOnlyNullValues()) {
// If search provide results
database.createVirtualGroupFromSearchInfo(
searchInfo.getSearchString(context),
MAX_SEARCH_ENTRY
)?.let { searchGroup ->
if (searchGroup.getNumberOfChildEntries() > 0) {
searchWithoutUI = true
onItemsFound.invoke(
searchGroup.getChildEntriesInfo(database))
}
}
}
if (!searchWithoutUI) {
onItemNotFound.invoke()
}
} else {
onDatabaseClosed.invoke()
}
}
}
private var incrementEntry = 0
fun createVirtualGroupWithSearchResult(database: Database, searchQuery: String, max: Int): Group? {
fun createVirtualGroupWithSearchResult(database: Database,
searchQuery: String,
searchParameters: SearchParameters,
max: Int): Group? {
val searchGroup = database.createGroup()
searchGroup?.title = "\"" + searchQuery + "\""
@@ -47,7 +89,7 @@ class SearchHelper(private val isOmitBackup: Boolean) {
override fun operate(node: Entry): Boolean {
if (incrementEntry >= max)
return false
if (entryContainsString(node, searchQuery)) {
if (entryContainsString(node, searchQuery, searchParameters)) {
searchGroup?.addChildEntry(node)
incrementEntry++
}
@@ -69,26 +111,28 @@ class SearchHelper(private val isOmitBackup: Boolean) {
return searchGroup
}
private fun entryContainsString(entry: Entry, searchString: String): Boolean {
private fun entryContainsString(entry: Entry,
searchQuery: String,
searchParameters: SearchParameters): Boolean {
// Entry don't contains string if the search string is empty
if (searchString.isEmpty())
if (searchQuery.isEmpty())
return false
// Search all strings in the entry
var iterator: EntrySearchStringIterator? = null
var iterator: Iterator<String>? = null
entry.entryKDB?.let {
iterator = EntrySearchStringIteratorKDB(it)
iterator = EntrySearchStringIteratorKDB(it, searchParameters)
}
entry.entryKDBX?.let {
iterator = EntrySearchStringIteratorKDBX(it)
iterator = EntrySearchStringIteratorKDBX(it, searchParameters)
}
iterator?.let {
while (it.hasNext()) {
val currentString = it.next()
if (currentString.isNotEmpty()
&& currentString.contains(searchString, true)) {
&& currentString.contains(searchQuery, true)) {
return true
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.search
/**
* Parameters for searching strings in the database.
*/
open class SearchParameters {
class SearchParameters {
var searchString: String = ""
@@ -33,6 +33,9 @@ open class SearchParameters {
var searchInUrls = true
var searchInGroupNames = false
var searchInNotes = true
var searchInOther = true
var searchInUUIDs = false
var searchInTags = true
var ignoreCase = true
var ignoreExpired = false
var excludeExpired = false
@@ -44,24 +47,30 @@ open class SearchParameters {
* @param source
*/
constructor(source: SearchParameters) {
regularExpression = source.regularExpression
searchInTitles = source.searchInTitles
searchInUserNames = source.searchInUserNames
searchInPasswords = source.searchInPasswords
searchInUrls = source.searchInUrls
searchInGroupNames = source.searchInGroupNames
searchInNotes = source.searchInNotes
ignoreCase = source.ignoreCase
ignoreExpired = source.ignoreExpired
excludeExpired = source.excludeExpired
this.regularExpression = source.regularExpression
this.searchInTitles = source.searchInTitles
this.searchInUserNames = source.searchInUserNames
this.searchInPasswords = source.searchInPasswords
this.searchInUrls = source.searchInUrls
this.searchInGroupNames = source.searchInGroupNames
this.searchInNotes = source.searchInNotes
this.searchInOther = source.searchInOther
this.searchInUUIDs = source.searchInUUIDs
this.searchInTags = source.searchInTags
this.ignoreCase = source.ignoreCase
this.ignoreExpired = source.ignoreExpired
this.excludeExpired = source.excludeExpired
}
open fun setupNone() {
fun setupNone() {
searchInTitles = false
searchInUserNames = false
searchInPasswords = false
searchInUrls = false
searchInGroupNames = false
searchInNotes = false
searchInOther = false
searchInUUIDs = false
searchInTags = false
}
}

View File

@@ -1,22 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.search.iterator
abstract class EntrySearchStringIterator : Iterator<String>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -24,12 +24,10 @@ import com.kunzisoft.keepass.database.search.SearchParameters
import java.util.NoSuchElementException
class EntrySearchStringIteratorKDB
@JvmOverloads
constructor(private val mEntry: EntryKDB,
private val mSearchParameters: SearchParameters? = SearchParameters())
: EntrySearchStringIterator() {
class EntrySearchStringIteratorKDB(
private val mEntry: EntryKDB,
private val mSearchParameters: SearchParameters)
: Iterator<String> {
private var current = 0
@@ -62,11 +60,6 @@ constructor(private val mEntry: EntryKDB,
}
private fun useSearchParameters() {
if (mSearchParameters == null) {
return
}
var found = false
while (!found) {
found = when (current) {
@@ -84,7 +77,6 @@ constructor(private val mEntry: EntryKDB,
}
companion object {
private const val title = 0
private const val url = 1
private const val username = 2

View File

@@ -20,25 +20,20 @@
package com.kunzisoft.keepass.database.search.iterator
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.search.SearchParametersKDBX
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.search.SearchParameters
import java.util.*
import kotlin.collections.Map.Entry
class EntrySearchStringIteratorKDBX : EntrySearchStringIterator {
class EntrySearchStringIteratorKDBX(
entry: EntryKDBX,
private val mSearchParameters: SearchParameters)
: Iterator<String> {
private var mCurrent: String? = null
private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null
private var mSearchParametersV4: SearchParametersKDBX
constructor(entry: EntryKDBX) {
this.mSearchParametersV4 = SearchParametersKDBX()
mSetIterator = entry.fields.entries.iterator()
advance()
}
constructor(entry: EntryKDBX, searchParametersV4: SearchParametersKDBX) {
this.mSearchParametersV4 = searchParametersV4
init {
mSetIterator = entry.fields.entries.iterator()
advance()
}
@@ -75,12 +70,12 @@ class EntrySearchStringIteratorKDBX : EntrySearchStringIterator {
private fun searchInField(key: String): Boolean {
return when (key) {
EntryKDBX.STR_TITLE -> mSearchParametersV4.searchInTitles
EntryKDBX.STR_USERNAME -> mSearchParametersV4.searchInUserNames
EntryKDBX.STR_PASSWORD -> mSearchParametersV4.searchInPasswords
EntryKDBX.STR_URL -> mSearchParametersV4.searchInUrls
EntryKDBX.STR_NOTES -> mSearchParametersV4.searchInNotes
else -> mSearchParametersV4.searchInOther
EntryKDBX.STR_TITLE -> mSearchParameters.searchInTitles
EntryKDBX.STR_USERNAME -> mSearchParameters.searchInUserNames
EntryKDBX.STR_PASSWORD -> mSearchParameters.searchInPasswords
EntryKDBX.STR_URL -> mSearchParameters.searchInUrls
EntryKDBX.STR_NOTES -> mSearchParameters.searchInNotes
else -> mSearchParameters.searchInOther
}
}

View File

@@ -62,7 +62,7 @@ class IconDrawableFactory {
fun assignDrawableToImageView(superDrawable: SuperDrawable, imageView: ImageView?, tint: Boolean, tintColor: Int) {
if (imageView != null) {
imageView.setImageDrawable(superDrawable.drawable)
if (!superDrawable.custom && tint) {
if (superDrawable.tintable && tint) {
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor))
} else {
ImageViewCompat.setImageTintList(imageView, null)
@@ -79,7 +79,7 @@ class IconDrawableFactory {
tintColor: Int = Color.BLACK) {
val bitmap = superDrawable.drawable.toBitmap()
// Tint bitmap if it's not a custom icon
if (!superDrawable.custom) {
if (superDrawable.tintable && bitmap.isMutable) {
Canvas(bitmap).drawBitmap(bitmap, 0.0F, 0.0F, Paint().apply {
colorFilter = PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)
})
@@ -97,7 +97,7 @@ class IconDrawableFactory {
getIconSuperDrawable(context, resId, width, tint, tintColor)
}
is IconImageCustom -> {
SuperDrawable(getIconDrawable(context.resources, icon), true)
SuperDrawable(getIconDrawable(context.resources, icon))
}
else -> {
SuperDrawable(PatternIcon(context.resources).blankDrawable)
@@ -110,7 +110,7 @@ class IconDrawableFactory {
* , then [tint] it with [tintColor] if needed
*/
fun getIconSuperDrawable(context: Context, iconId: Int, width: Int, tint: Boolean, tintColor: Int): SuperDrawable {
return SuperDrawable(getIconDrawable(context.resources, iconId, width, tint, tintColor))
return SuperDrawable(getIconDrawable(context.resources, iconId, width, tint, tintColor), true)
}
/**
@@ -236,7 +236,7 @@ class IconDrawableFactory {
/**
* Utility class to prevent a custom icon to be tint
*/
class SuperDrawable(var drawable: Drawable, var custom: Boolean = false)
class SuperDrawable(var drawable: Drawable, var tintable: Boolean = false)
companion object {
@@ -248,17 +248,22 @@ class IconDrawableFactory {
/**
* Assign a default database icon to an ImageView and tint it with [tintColor] if needed
*/
fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory, tintColor: Int = Color.WHITE) {
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
iconFactory.assignDrawableToImageView(
iconFactory.getIconSuperDrawable(context,
selectedIconPack.defaultIconId,
width,
selectedIconPack.tintable(),
tintColor),
this,
selectedIconPack.tintable(),
tintColor)
fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory,
tintColor: Int = Color.WHITE) {
try {
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
iconFactory.assignDrawableToImageView(
iconFactory.getIconSuperDrawable(context,
selectedIconPack.defaultIconId,
width,
selectedIconPack.tintable(),
tintColor),
this,
selectedIconPack.tintable(),
tintColor)
}
} catch (e: Exception) {
Log.e(ImageView::class.java.name, "Unable to assign icon in image view", e)
}
}
@@ -268,16 +273,20 @@ fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory, tintCo
fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory,
icon: IconImage,
tintColor: Int = Color.WHITE) {
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
iconFactory.assignDrawableToImageView(
iconFactory.getIconSuperDrawable(context,
icon,
width,
true,
tintColor),
this,
selectedIconPack.tintable(),
tintColor)
try {
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
iconFactory.assignDrawableToImageView(
iconFactory.getIconSuperDrawable(context,
icon,
width,
true,
tintColor),
this,
selectedIconPack.tintable(),
tintColor)
}
} catch (e: Exception) {
Log.e(ImageView::class.java.name, "Unable to assign icon in image view", e)
}
}
@@ -286,6 +295,7 @@ fun RemoteViews.assignDatabaseIcon(context: Context,
iconFactory: IconDrawableFactory,
icon: IconImage,
tintColor: Int = Color.BLACK) {
try {
iconFactory.assignDrawableToRemoteViews(
iconFactory.getIconSuperDrawable(context,
icon,
@@ -295,4 +305,7 @@ fun RemoteViews.assignDatabaseIcon(context: Context,
this,
imageId,
tintColor)
} catch (e: Exception) {
Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
}
}

View File

@@ -36,6 +36,7 @@ import android.widget.FrameLayout
import android.widget.PopupWindow
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity
import com.kunzisoft.keepass.adapters.FieldsAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.EntryInfo
@@ -95,7 +96,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) {
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
goNextAutomatically()
actionTabAutomatically()
}
}
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
@@ -211,7 +212,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
// Stop current service and reinit entry
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
removeEntryInfo()
val intent = Intent(this, KeyboardLauncherActivity::class.java)
val intent = Intent(this, MagikeyboardLauncherActivity::class.java)
// New task needed because don't launch from an Activity context
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
@@ -225,19 +226,19 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.username, 1)
}
goNextAutomatically()
actionTabAutomatically()
}
KEY_PASSWORD -> {
if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.password, 1)
}
goNextAutomatically()
actionGoAutomatically()
}
KEY_URL -> {
if (entryInfoKey != null) {
currentInputConnection.commitText(entryInfoKey!!.url, 1)
}
goNextAutomatically()
actionTabAutomatically()
}
KEY_FIELDS -> {
if (entryInfoKey != null) {
@@ -248,12 +249,19 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
}
popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0)
}
Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0)
Keyboard.KEYCODE_DELETE -> {
inputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
}
Keyboard.KEYCODE_DONE -> inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
}// TODO Unlock key
}
}
private fun goNextAutomatically() {
private fun actionTabAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
}
private fun actionGoAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
}
@@ -316,11 +324,11 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
context.sendBroadcast(Intent(REMOVE_ENTRY_MAGIKEYBOARD_ACTION))
}
fun addEntryAndLaunchNotificationIfAllowed(context: Context, entry: EntryInfo) {
fun addEntryAndLaunchNotificationIfAllowed(context: Context, entry: EntryInfo, toast: Boolean = false) {
// Add a new entry
entryInfoKey = entry
// Launch notification if allowed
KeyboardEntryNotificationService.launchNotificationIfAllowed(context, entry)
KeyboardEntryNotificationService.launchNotificationIfAllowed(context, entry, toast)
}
}
}

View File

@@ -1,12 +1,31 @@
package com.kunzisoft.keepass.model
import android.content.Context
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.UriUtil
class SearchInfo : Parcelable {
class SearchInfo : ObjectNameResource, Parcelable {
var applicationId: String? = null
set(value) {
field = when {
value == null -> null
Regex(APPLICATION_ID_REGEX).matches(value) -> value
else -> null
}
}
var webDomain: String? = null
set(value) {
field = when {
value == null -> null
Regex(WEB_DOMAIN_REGEX).matches(value) -> value
else -> null
}
}
constructor()
@@ -26,7 +45,40 @@ class SearchInfo : Parcelable {
parcel.writeString(webDomain ?: "")
}
override fun getName(resources: Resources): String {
return toString()
}
fun containsOnlyNullValues(): Boolean {
return applicationId == null && webDomain == null
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SearchInfo
if (applicationId != other.applicationId) return false
if (webDomain != other.webDomain) return false
return true
}
override fun hashCode(): Int {
var result = applicationId?.hashCode() ?: 0
result = 31 * result + (webDomain?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return webDomain ?: applicationId ?: ""
}
companion object {
// https://gist.github.com/rishabhmhjn/8663966
const val APPLICATION_ID_REGEX = "^(?:[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)(?:\\.[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)+\$"
const val WEB_DOMAIN_REGEX = "^(?!://)([a-zA-Z0-9-_]+\\.)*[a-zA-Z0-9][a-zA-Z0-9-_]+\\.[a-zA-Z]{2,11}?\$"
@JvmField
val CREATOR: Parcelable.Creator<SearchInfo> = object : Parcelable.Creator<SearchInfo> {
@@ -39,4 +91,15 @@ class SearchInfo : Parcelable {
}
}
}
}
fun SearchInfo.getSearchString(context: Context): String {
return run {
if (!PreferencesUtil.searchSubdomains(context))
UriUtil.getWebDomainWithoutSubDomain(webDomain)
else
webDomain
}
?: applicationId
?: ""
}

View File

@@ -81,6 +81,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val downloadFileUri: Uri? = if (intent?.hasExtra(DOWNLOAD_FILE_URI_KEY) == true) {
intent.getParcelableExtra(DOWNLOAD_FILE_URI_KEY)

View File

@@ -55,6 +55,8 @@ class ClipboardEntryNotificationService : LockNotificationService() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
// Get entry info from intent
mEntryInfo = intent?.getParcelableExtra(EXTRA_ENTRY_INFO)

View File

@@ -48,6 +48,7 @@ class DatabaseOpenNotificationService: LockNotificationService() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
when(intent?.action) {
ACTION_CLOSE_DATABASE -> {

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