mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
619 Commits
2.5.0.0bet
...
2.5.0.0bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
462c29b769 | ||
|
|
3939276d58 | ||
|
|
ff85f18c4c | ||
|
|
dd14fe4123 | ||
|
|
d804659ee2 | ||
|
|
e3152cf901 | ||
|
|
de236f321f | ||
|
|
66f4611866 | ||
|
|
6a6ef052af | ||
|
|
4e4606dabd | ||
|
|
0ce11103ab | ||
|
|
899d0e0557 | ||
|
|
4aefeff41f | ||
|
|
08d59e50e8 | ||
|
|
e6c06aba8c | ||
|
|
e5c2a04922 | ||
|
|
c134ccf8d9 | ||
|
|
4f959d1ff6 | ||
|
|
1c06d93951 | ||
|
|
b2dff29baa | ||
|
|
72633e9472 | ||
|
|
898a88f7d8 | ||
|
|
c2f09f6666 | ||
|
|
d45bcbc347 | ||
|
|
1f834567f8 | ||
|
|
0b62c04867 | ||
|
|
c71bb2a570 | ||
|
|
0a575b5bf8 | ||
|
|
6f2bf903c5 | ||
|
|
210ad4b8db | ||
|
|
b42cf0e204 | ||
|
|
2045d3aab8 | ||
|
|
e20cb9431d | ||
|
|
7161eaea8b | ||
|
|
b786da52f5 | ||
|
|
ccbfec838d | ||
|
|
f5c1872225 | ||
|
|
3001013bad | ||
|
|
1280803e5e | ||
|
|
f84fc07fe0 | ||
|
|
d83e53f589 | ||
|
|
52ba2c53f1 | ||
|
|
f266e1de4c | ||
|
|
5a0aafd3ce | ||
|
|
c7ce07a43c | ||
|
|
a73644c285 | ||
|
|
e28f1ffc99 | ||
|
|
afc691b2f4 | ||
|
|
00f6eb83c3 | ||
|
|
bbf51ebbec | ||
|
|
7816c8f16e | ||
|
|
fdbcba2412 | ||
|
|
50dc6cb0aa | ||
|
|
e413198d12 | ||
|
|
72592772f9 | ||
|
|
f918206bcd | ||
|
|
6b34d67da8 | ||
|
|
04fb093d4d | ||
|
|
7de0e0cc4a | ||
|
|
5cf7688e38 | ||
|
|
97cd06b52b | ||
|
|
6d522ead1d | ||
|
|
8e0fae62f3 | ||
|
|
af72098d60 | ||
|
|
62da582f4e | ||
|
|
f3efa6eddc | ||
|
|
c85fb7bd0a | ||
|
|
fc7b183461 | ||
|
|
a378810f88 | ||
|
|
2f5b322fad | ||
|
|
efb9b50f85 | ||
|
|
84fdef8eb6 | ||
|
|
76050a261b | ||
|
|
2667361450 | ||
|
|
a2b3313cc0 | ||
|
|
d343446235 | ||
|
|
cbce70f8a4 | ||
|
|
4573d6b6fb | ||
|
|
973305d13c | ||
|
|
4637016372 | ||
|
|
698ba4f7f1 | ||
|
|
502ebabf1f | ||
|
|
13f88626ca | ||
|
|
a39f58f7b5 | ||
|
|
aeb36468ce | ||
|
|
fdd196526d | ||
|
|
ecca892b16 | ||
|
|
03343d0301 | ||
|
|
a61c8b0cb6 | ||
|
|
a92129da00 | ||
|
|
0b783d6390 | ||
|
|
9aa1a2e30e | ||
|
|
15b3c69514 | ||
|
|
f2722e09ec | ||
|
|
b442859be0 | ||
|
|
1496a2efb1 | ||
|
|
a0edb111f0 | ||
|
|
6bcf54fe29 | ||
|
|
3d7e24816a | ||
|
|
f5e02ec63f | ||
|
|
ed1f4ebace | ||
|
|
eb0e496cfd | ||
|
|
8b9fc30a6d | ||
|
|
12c2a6e99c | ||
|
|
714433b62d | ||
|
|
e42933d786 | ||
|
|
e9531e4edd | ||
|
|
0cd9cd294d | ||
|
|
b03fb12fca | ||
|
|
b7b2e8dc4e | ||
|
|
96568abc51 | ||
|
|
a180688858 | ||
|
|
2590067558 | ||
|
|
b5499238f7 | ||
|
|
cc5b96f539 | ||
|
|
32343dc937 | ||
|
|
1e71dd3031 | ||
|
|
ebf6f6a52a | ||
|
|
6bf6d661af | ||
|
|
fe16879f35 | ||
|
|
ead384d1bb | ||
|
|
1ebdc0bacd | ||
|
|
8eca8cdd53 | ||
|
|
24c61b1b37 | ||
|
|
ea18cc7166 | ||
|
|
387c499829 | ||
|
|
339470dd6e | ||
|
|
02d1089dbc | ||
|
|
1bc0932cec | ||
|
|
76cc2739c8 | ||
|
|
b23908aec2 | ||
|
|
6b1b8c0f7b | ||
|
|
06320a7eba | ||
|
|
fc02145d0c | ||
|
|
5360738775 | ||
|
|
0957df752a | ||
|
|
fe56d06b5d | ||
|
|
f4955b16cd | ||
|
|
07692ba73d | ||
|
|
64ac3e8f32 | ||
|
|
8013d3109a | ||
|
|
be6f01dc99 | ||
|
|
30f7779ec6 | ||
|
|
6b5823ca70 | ||
|
|
d4a09ed569 | ||
|
|
84fe409c4b | ||
|
|
51d90891ad | ||
|
|
b1fa06099c | ||
|
|
fa9d8ad6ad | ||
|
|
6703d7b451 | ||
|
|
73afd44d9c | ||
|
|
93cb93bb9b | ||
|
|
82902cff71 | ||
|
|
3657f7e54c | ||
|
|
a57a2f738d | ||
|
|
b93592d703 | ||
|
|
fd288e624b | ||
|
|
095fa3f6ef | ||
|
|
b1f6c578ad | ||
|
|
1bc991bfcb | ||
|
|
02feb478e8 | ||
|
|
c2df79f0c9 | ||
|
|
ef13965747 | ||
|
|
13421601de | ||
|
|
5a9b46c4b5 | ||
|
|
6268642097 | ||
|
|
12eadbc092 | ||
|
|
c9475d1dc2 | ||
|
|
56805defb6 | ||
|
|
477a784201 | ||
|
|
f54bac15c9 | ||
|
|
ae28ebe701 | ||
|
|
f16adf00da | ||
|
|
d17699f6f7 | ||
|
|
8afcf043ee | ||
|
|
dda9d034aa | ||
|
|
652bad51b4 | ||
|
|
4d2ccc0789 | ||
|
|
0e1c21e0f4 | ||
|
|
8d3f58b2cc | ||
|
|
be78905d85 | ||
|
|
b3f232c840 | ||
|
|
075a16c1c3 | ||
|
|
18a13e6266 | ||
|
|
7149bdbc3a | ||
|
|
9a4c4aa9bf | ||
|
|
2b32cab9d1 | ||
|
|
66611db261 | ||
|
|
fdc2095b42 | ||
|
|
2f9cab0da2 | ||
|
|
bd0d474751 | ||
|
|
cca0ab2669 | ||
|
|
cb5ca575d5 | ||
|
|
f4caaad9ee | ||
|
|
b9cfb32a20 | ||
|
|
095e5e5dd6 | ||
|
|
ffc58688d8 | ||
|
|
6a69a7f416 | ||
|
|
b4188b4712 | ||
|
|
4a22c28df4 | ||
|
|
76e9a25b1a | ||
|
|
1928d0823e | ||
|
|
c183d22412 | ||
|
|
b684353721 | ||
|
|
72f0e871c7 | ||
|
|
9a63962903 | ||
|
|
938de28b49 | ||
|
|
20fc094d71 | ||
|
|
40180d5883 | ||
|
|
59e5865318 | ||
|
|
f63d6bdc1d | ||
|
|
fe33c0ae7d | ||
|
|
ca4ad1c1fd | ||
|
|
adf5382804 | ||
|
|
7f5406ac98 | ||
|
|
23b21ea154 | ||
|
|
49d4d0421a | ||
|
|
23859a61bb | ||
|
|
221f81f51e | ||
|
|
6e7c0d5073 | ||
|
|
e8e3d53685 | ||
|
|
e6d9df2b98 | ||
|
|
477a8f2e38 | ||
|
|
5e66697b8b | ||
|
|
16320abb7d | ||
|
|
f122c2832c | ||
|
|
d84c561f44 | ||
|
|
da49c9c045 | ||
|
|
553920e37c | ||
|
|
450d2d113b | ||
|
|
744c80e34d | ||
|
|
c0f8cca7c6 | ||
|
|
b129f220f7 | ||
|
|
7a3df02e38 | ||
|
|
befd29c396 | ||
|
|
b8245621ea | ||
|
|
ecda25a743 | ||
|
|
d97a85b997 | ||
|
|
8c0d7ab9ed | ||
|
|
f3fa73ea34 | ||
|
|
788734ccad | ||
|
|
e088f4a4ad | ||
|
|
86bd018e4e | ||
|
|
283145034d | ||
|
|
163162497e | ||
|
|
56911fb58f | ||
|
|
dae6481aff | ||
|
|
6b2eb5e4f6 | ||
|
|
c563787f73 | ||
|
|
2737755b85 | ||
|
|
fd9486ca77 | ||
|
|
14020ec0b5 | ||
|
|
5a6c466ebd | ||
|
|
76fcd5fe19 | ||
|
|
3732ff1ebc | ||
|
|
22dd09954b | ||
|
|
ef7387f2f3 | ||
|
|
f774298587 | ||
|
|
f8134307f6 | ||
|
|
fe461f2e7c | ||
|
|
023c841747 | ||
|
|
af95c0903a | ||
|
|
0d756db8aa | ||
|
|
2c5dcc9b11 | ||
|
|
21c6ea73b2 | ||
|
|
51dc302bb0 | ||
|
|
87760ab4f6 | ||
|
|
88ebe58a88 | ||
|
|
fb023b81b5 | ||
|
|
2de6bbc6c0 | ||
|
|
4ef436629d | ||
|
|
9fd342f1e7 | ||
|
|
8988f17765 | ||
|
|
9d160db281 | ||
|
|
2e58c2f1b3 | ||
|
|
d1d2b99e09 | ||
|
|
def9744f75 | ||
|
|
214e2cf109 | ||
|
|
b25180c617 | ||
|
|
6a5263df77 | ||
|
|
2982f67717 | ||
|
|
b559670dff | ||
|
|
891d3142d2 | ||
|
|
2637788429 | ||
|
|
a21de3b892 | ||
|
|
e087e19120 | ||
|
|
721d61dda7 | ||
|
|
e0e7e431cf | ||
|
|
93948e7c61 | ||
|
|
b150c718a0 | ||
|
|
a71e4c3902 | ||
|
|
b7dc13d863 | ||
|
|
1e3c58e359 | ||
|
|
5f75599e9f | ||
|
|
b602f9b77d | ||
|
|
aa948c1ece | ||
|
|
e599a51152 | ||
|
|
ee6052f4d1 | ||
|
|
eba527f477 | ||
|
|
09e0d6d3cc | ||
|
|
9aefc984be | ||
|
|
2ce3b21f1b | ||
|
|
4d2f3cb4b1 | ||
|
|
e62b46c4c0 | ||
|
|
6472601170 | ||
|
|
89dd7bfefb | ||
|
|
fb2ea4c0ed | ||
|
|
8d84358d48 | ||
|
|
1d8661c633 | ||
|
|
48130eee45 | ||
|
|
2cf83962fe | ||
|
|
aecf7c0c39 | ||
|
|
39606e2676 | ||
|
|
6e00fa2d01 | ||
|
|
f79aa339e9 | ||
|
|
f412fce912 | ||
|
|
cc20b7503c | ||
|
|
2573434763 | ||
|
|
f153c26fef | ||
|
|
125f461cbe | ||
|
|
b705b4b712 | ||
|
|
c67b0bb858 | ||
|
|
ab1fc8c5d5 | ||
|
|
8477f4ba08 | ||
|
|
e6518ffdc8 | ||
|
|
99917c7f28 | ||
|
|
fcc29f67a3 | ||
|
|
7dd49f050c | ||
|
|
5f96de84b0 | ||
|
|
54c2f5a61f | ||
|
|
921c6f88aa | ||
|
|
a0cb579df4 | ||
|
|
d6a7c34ff3 | ||
|
|
bf2e61f149 | ||
|
|
eaf5dc5988 | ||
|
|
879ee013db | ||
|
|
e13d53eae4 | ||
|
|
d72c8184c9 | ||
|
|
c4d3c8cbfb | ||
|
|
02a3d85f80 | ||
|
|
19b0722f1f | ||
|
|
f14222b192 | ||
|
|
4f4f6d30d9 | ||
|
|
fdd329e982 | ||
|
|
55a4d388b3 | ||
|
|
5c6be448ec | ||
|
|
3e79ddcc21 | ||
|
|
5362758424 | ||
|
|
c10e3df2a7 | ||
|
|
166784021a | ||
|
|
5615c31e08 | ||
|
|
fb60dd5921 | ||
|
|
ff4c1b779b | ||
|
|
53a7b99567 | ||
|
|
a57103bafb | ||
|
|
2540f32dbf | ||
|
|
499ccd6b7c | ||
|
|
a4359560b9 | ||
|
|
149483cc2d | ||
|
|
a1d2022492 | ||
|
|
891036c35c | ||
|
|
4e16ba5f56 | ||
|
|
7137a2fadb | ||
|
|
9d90d0eaba | ||
|
|
94a9942db5 | ||
|
|
5f347fe106 | ||
|
|
a34a84ae16 | ||
|
|
40b0982298 | ||
|
|
4100258476 | ||
|
|
5f3f6661b7 | ||
|
|
75af97e0ae | ||
|
|
58f158c457 | ||
|
|
ce27eae1f0 | ||
|
|
0aa0b3e993 | ||
|
|
1cc5a08236 | ||
|
|
4c587eeb03 | ||
|
|
ab70c2d014 | ||
|
|
8413160ac5 | ||
|
|
5abc403171 | ||
|
|
9b891013b8 | ||
|
|
9413987355 | ||
|
|
f95b514b41 | ||
|
|
f6985c8944 | ||
|
|
4388d56c52 | ||
|
|
a70fe24c97 | ||
|
|
8e0392753c | ||
|
|
9c9980bba6 | ||
|
|
2226c15d29 | ||
|
|
82a859bd9c | ||
|
|
83873fab81 | ||
|
|
31f2be7b91 | ||
|
|
16458e6646 | ||
|
|
2b9678707d | ||
|
|
cdbb23d7f1 | ||
|
|
23fd1b83f4 | ||
|
|
40b0ebe49b | ||
|
|
7cd8682544 | ||
|
|
d0dd478ac8 | ||
|
|
ffb547c452 | ||
|
|
bd829f129f | ||
|
|
5ad3f62de5 | ||
|
|
b0ec4942bc | ||
|
|
2cbc9675f6 | ||
|
|
116643a45a | ||
|
|
2f0eb283ed | ||
|
|
6d46fccdcd | ||
|
|
f5dc94bfec | ||
|
|
94bdb0e3da | ||
|
|
65360c2a1e | ||
|
|
70d30bdbe6 | ||
|
|
66f7e6d1b1 | ||
|
|
a8ccb67a87 | ||
|
|
66051382f1 | ||
|
|
0fb3028c91 | ||
|
|
5e5baa4892 | ||
|
|
9d1257ed9d | ||
|
|
9d7546053d | ||
|
|
a1b692abe5 | ||
|
|
4e06842d0f | ||
|
|
f04c2ee1da | ||
|
|
9700dbcc3f | ||
|
|
4e344458b2 | ||
|
|
6f95cc7296 | ||
|
|
cd66f8f57e | ||
|
|
83f0eb9a33 | ||
|
|
ca89bba768 | ||
|
|
2d2bd5013e | ||
|
|
b93d7bbf41 | ||
|
|
000277705a | ||
|
|
088712e784 | ||
|
|
117592387e | ||
|
|
76bb1a369c | ||
|
|
9331c281fe | ||
|
|
f2666316e1 | ||
|
|
7c2ff5067d | ||
|
|
5fed641c7c | ||
|
|
18bd62ee5a | ||
|
|
bc57e6e257 | ||
|
|
69b1aba218 | ||
|
|
df04d998c2 | ||
|
|
e986fe5f60 | ||
|
|
e4ac0ee258 | ||
|
|
426aa0e7da | ||
|
|
6c0a48af48 | ||
|
|
d865da1613 | ||
|
|
6fa4c1e06e | ||
|
|
52a6b3e046 | ||
|
|
fa26d2f938 | ||
|
|
4777cdc7ae | ||
|
|
cd8d3cbf6a | ||
|
|
e6a6feb5c0 | ||
|
|
98d6fb9214 | ||
|
|
94244cd15b | ||
|
|
c5e2ca9907 | ||
|
|
e4fef44caf | ||
|
|
5bd9da9bb1 | ||
|
|
5f0e899679 | ||
|
|
4b1806900b | ||
|
|
30e2912885 | ||
|
|
fc3608ff69 | ||
|
|
6de47ec9b2 | ||
|
|
f7253764a2 | ||
|
|
c54b134c31 | ||
|
|
a6bdca52be | ||
|
|
29eec05f8f | ||
|
|
250aef9738 | ||
|
|
50097914a2 | ||
|
|
ecff4fb2c5 | ||
|
|
ab3d17f352 | ||
|
|
a75d237e53 | ||
|
|
a0c7786e1b | ||
|
|
0d32c38c79 | ||
|
|
b846eda410 | ||
|
|
7c33c9ec02 | ||
|
|
74c08340a6 | ||
|
|
82161536be | ||
|
|
752dbca356 | ||
|
|
9ef56f6fd8 | ||
|
|
0239f115ae | ||
|
|
9775e09221 | ||
|
|
c975a1bfc0 | ||
|
|
c263536078 | ||
|
|
b559eeaad0 | ||
|
|
63373083ab | ||
|
|
bf525807b0 | ||
|
|
921021078b | ||
|
|
e5b60a8413 | ||
|
|
e01402f2fa | ||
|
|
27f92e1bb5 | ||
|
|
63db6de30b | ||
|
|
7a038126cf | ||
|
|
edcfa8cf7b | ||
|
|
c9594948a2 | ||
|
|
66988ecb66 | ||
|
|
186ca30be8 | ||
|
|
027d581dcc | ||
|
|
adcc1c745a | ||
|
|
8682856c01 | ||
|
|
9ec976d246 | ||
|
|
9d17b49586 | ||
|
|
698496d37c | ||
|
|
2af8c4f3c8 | ||
|
|
b164099b6d | ||
|
|
c19357605f | ||
|
|
5a08fa0088 | ||
|
|
bff87d16b1 | ||
|
|
5b0afa447c | ||
|
|
5a882a954f | ||
|
|
47f340d576 | ||
|
|
41df139c17 | ||
|
|
469c267161 | ||
|
|
f336d4fe58 | ||
|
|
e1d997cc91 | ||
|
|
53cf4bba1b | ||
|
|
030417dbe1 | ||
|
|
59abcb115c | ||
|
|
598dbd3794 | ||
|
|
2c4a7e5576 | ||
|
|
02429d5790 | ||
|
|
d990cb24ea | ||
|
|
e549b16dce | ||
|
|
e11864a64f | ||
|
|
a3e74f8ee5 | ||
|
|
36cb683404 | ||
|
|
f7f0028033 | ||
|
|
f06088fa12 | ||
|
|
b62ef8a2ed | ||
|
|
622ba65841 | ||
|
|
3a48d20d12 | ||
|
|
8d6db78f55 | ||
|
|
f3ba6e800a | ||
|
|
1c4aaf9807 | ||
|
|
c65ed41efd | ||
|
|
1965336077 | ||
|
|
9b7095ad4c | ||
|
|
33767c2bf9 | ||
|
|
82f7e861e7 | ||
|
|
846b5fa449 | ||
|
|
baf4e676eb | ||
|
|
a9bf3e83c4 | ||
|
|
710a1b0996 | ||
|
|
c4671b84a0 | ||
|
|
c0c98d0299 | ||
|
|
ffdec77d11 | ||
|
|
868bbe2e70 | ||
|
|
4aac655a5d | ||
|
|
510244aa70 | ||
|
|
50840b04b4 | ||
|
|
4d5962f5ca | ||
|
|
c9456c771c | ||
|
|
41a7a583d4 | ||
|
|
2e5220fa8a | ||
|
|
b75475d785 | ||
|
|
362ef06bb5 | ||
|
|
4a80c9f9f9 | ||
|
|
f1fdb9fc84 | ||
|
|
5bb9168c29 | ||
|
|
0245dcd8e8 | ||
|
|
b31bfa1d4f | ||
|
|
0778f22b68 | ||
|
|
4808696398 | ||
|
|
0ea7b5b25f | ||
|
|
995785de9f | ||
|
|
5deef427c0 | ||
|
|
024c6631d8 | ||
|
|
78354e4736 | ||
|
|
b9a792e6bd | ||
|
|
c533d21250 | ||
|
|
74572c8102 | ||
|
|
b8de64fab0 | ||
|
|
877b909205 | ||
|
|
1b1dcc0f45 | ||
|
|
94e5988794 | ||
|
|
b22333fda5 | ||
|
|
400c6bef78 | ||
|
|
edf6c2ff07 | ||
|
|
5eec1a276c | ||
|
|
f707fd7649 | ||
|
|
75b028daf3 | ||
|
|
c6f259d18f | ||
|
|
954d522341 | ||
|
|
1f8d17d27e | ||
|
|
c2781af38d | ||
|
|
a1cd2683d4 | ||
|
|
6d671f53c2 | ||
|
|
4efb81f597 | ||
|
|
faddeb6e2d | ||
|
|
e36629968d | ||
|
|
0b756797f6 | ||
|
|
110c7bbbc7 | ||
|
|
639c6dc4ac | ||
|
|
f605d36adf | ||
|
|
f6d6c134f4 | ||
|
|
a0c07654df | ||
|
|
03ab688abe | ||
|
|
4a28802b02 | ||
|
|
18f8fe7cc3 | ||
|
|
459606f5d5 | ||
|
|
3d53c31680 | ||
|
|
173dd5b59b | ||
|
|
8536de3555 | ||
|
|
722ba5c34d | ||
|
|
3ccfd7226b | ||
|
|
578da7bae5 | ||
|
|
6916389657 | ||
|
|
da4f41732a | ||
|
|
0414adf5de | ||
|
|
d9b2942f66 | ||
|
|
cbe7907b07 | ||
|
|
b737501d4d | ||
|
|
c303ffafb5 | ||
|
|
6f513b4920 | ||
|
|
a83c60583f | ||
|
|
cde8950257 | ||
|
|
0b4dd1e909 | ||
|
|
28e2600271 | ||
|
|
53cc4f74c8 | ||
|
|
3989ca3ad1 | ||
|
|
9558fcaf21 |
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
** Keepass Database **
|
||||
- Created with: [e.g Windows KeePass 2.42]
|
||||
- Version: [e.g. 2]
|
||||
- Location: [e.g. Remote file retrieved with GDrive app]
|
||||
- Size: [e.g. 150Mo]
|
||||
- Contains attachment: [e.g. Yes]
|
||||
|
||||
**KeePassDX (please complete the following information):**
|
||||
- Version: [e.g. 2.5.0.0beta23]
|
||||
- Build: [e.g. Free]
|
||||
- Language: [e.g. French]
|
||||
|
||||
**Android (please complete the following information):**
|
||||
- Device: [e.g. GalaxyS8]
|
||||
- Version: [e.g. 8.1]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
- Browser for Autofill: [e.g. Chrome version X]
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -38,6 +38,13 @@ proguard/
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# Eclipse/VS Code
|
||||
.project
|
||||
.settings/*
|
||||
*/.project
|
||||
*/.classpath
|
||||
*/.settings/*
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
|
||||
58
CHANGELOG
58
CHANGELOG
@@ -1,3 +1,61 @@
|
||||
KeepassDX (2.5.0.0beta26)
|
||||
* Download attachments
|
||||
* Change file size string format
|
||||
* Prevent screenshot for all screen
|
||||
* Auto performed "Go" key in Magikeyboard
|
||||
* Restore and delete entry history
|
||||
* Setting to hide expired entries
|
||||
* New Black theme
|
||||
* Fix crash when clearing clipboard
|
||||
* Fix attachments compressions
|
||||
* Fix dates
|
||||
* Fix UUID message for Database v1
|
||||
|
||||
KeepassDX (2.5.0.0beta25)
|
||||
* Setting for Recycle Bin
|
||||
* Fix Recycle bin issues
|
||||
* Fix TOTP
|
||||
* Fix infinite save
|
||||
* Fix update group
|
||||
* Fix OOM
|
||||
|
||||
KeepassDX (2.5.0.0beta24)
|
||||
* Add OTP (HOTP / TOTP)
|
||||
* Add settings (Color, Security, Master Key)
|
||||
* Show history of each entry
|
||||
* Auto repair database for nodes with same UUID
|
||||
* Management of expired nodes
|
||||
* Multi-selection for actions (Cut - Copy - Delete)
|
||||
* Open/Save database as service / Add persistent notification
|
||||
* Fix settings / edit group / small bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta23)
|
||||
* New, more secure database creation workflow
|
||||
* Recognize more database files
|
||||
* Add alias for history files (WARNING: history is erased)
|
||||
* New Biometric unlock (Fingerprint with new API)
|
||||
* Fix entry references
|
||||
* Fix OOM with KeyFile
|
||||
* Fix small issues
|
||||
|
||||
KeepassDX (2.5.0.0beta22)
|
||||
* Rebuild code for actions
|
||||
* Add UUID as entry view
|
||||
* Fix bug with natural order
|
||||
* Fix number of entries in databaseV1
|
||||
* New entry views
|
||||
|
||||
KeepassDX (2.5.0.0beta21)
|
||||
* Fix nested groups no longer visible in V1 databases
|
||||
* Improved data import algorithm for V1 databases
|
||||
* Add natural database sort
|
||||
* Add username database sort
|
||||
* Fix button disabled with only KeyFile
|
||||
* Show the number of entries in a group
|
||||
|
||||
KeepassDX (2.5.0.0beta20)
|
||||
* Fix a major bug that displays an entry history
|
||||
|
||||
KeepassDX (2.5.0.0beta19)
|
||||
* Add lock button always visible
|
||||
* New connection workflow
|
||||
|
||||
45
CONTRIBUTORS
45
CONTRIBUTORS
@@ -1,45 +0,0 @@
|
||||
Original author:
|
||||
Brian Pellin
|
||||
|
||||
Achim Weimert
|
||||
Johan Berts - search patches
|
||||
Mike Mohr - Better native code for aes and sha
|
||||
Tobias Selig - icon support
|
||||
Tolga Onbay, Dirk Bergstrom - password generator
|
||||
Space Cowboy - holo theme
|
||||
josefwells
|
||||
Nicholas FitzRoy-Dale - auto launch intents
|
||||
yulin2 - responsiveness improvements
|
||||
Tadashi Saito
|
||||
vhschlenker
|
||||
bumper314 - Samsung multiwindow support
|
||||
Hans Cappelle - fingerprint sensor integration
|
||||
Jeremy Jamet - Keepass DX Material Design - Patches
|
||||
|
||||
Translations:
|
||||
Diego Pierotto - Italian
|
||||
Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French
|
||||
Maciej Bieniek, cod3r - Polish
|
||||
Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian
|
||||
MaWi, rvs2008, meviox, MaDill, EdlerProgrammierer, Jan Thomas - German
|
||||
yslandro - Norwegian Nynorsk
|
||||
王科峰 - Chinese
|
||||
Typhoon - Slovak
|
||||
Masahiro Inamura - Japanese
|
||||
Matsuu Takuto - Japanese
|
||||
Carlos Schlyter - Portugese (Brazil)
|
||||
YSmhXQDd6Z - Portugese (Portugal)
|
||||
andriykopanytsia - Ukranian
|
||||
intel, Zoltán Antal - Hungarian
|
||||
H Vanek - Czech
|
||||
jipanos - Spanish
|
||||
Erik Fdevriendt, Erik Jan Meijer - Dutch
|
||||
Frederik Svarre - Danish
|
||||
Oriol Garrote - Catalan
|
||||
Mika Takala - Finnish
|
||||
Niclas Burgren - Swedish
|
||||
Raimonds - Latvian
|
||||
dgarciabad - Basque
|
||||
Arthur Zamarin - Hebrew
|
||||
RaptorTFX - Greek
|
||||
zygimantus - Lithuanian
|
||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
||||
---
|
||||
|
||||
KeePass DX is free software: you can redistribute it and/or modify
|
||||
KeePassDX is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
@@ -13,7 +13,7 @@ KeePass DX is free software: you can redistribute it and/or modify
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The KeePass DX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3.
|
||||
The KeePassDX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3.
|
||||
|
||||
---
|
||||
|
||||
|
||||
31
ReadMe.md
31
ReadMe.md
@@ -1,6 +1,6 @@
|
||||
# Android Keepass DX
|
||||
# Android KeepassDX
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> Keepass DX is a **multi-format KeePass manager for Android devices**. The application allows to create keys and passwords in a secure way by integrating with the Android design standards.
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeepassDX is a **multi-format KeePass manager for Android devices**. The application allows to create keys and passwords in a secure way by integrating with the Android design standards.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
|
||||
|
||||
@@ -8,26 +8,27 @@
|
||||
|
||||
* Create database files / entries and groups
|
||||
* Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
|
||||
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePass XC...)
|
||||
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
|
||||
* Allows **fast copy** of fields and opening of URI / URL
|
||||
* **Fingerprint** for fast unlocking
|
||||
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
|
||||
* **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
|
||||
* Material design with **themes**
|
||||
* **AutoFill** and Integration
|
||||
* Field filling **keyboard**
|
||||
* Precise management of **settings**
|
||||
* Code written in **native language** *(Kotlin / Java / JNI / C)*
|
||||
|
||||
Keepass DX is **open source** and **ad-free**.
|
||||
KeepassDX is **open source** and **ad-free**.
|
||||
|
||||
## What is KeePass DX?
|
||||
## What is KeePassDX?
|
||||
|
||||
Today you need to remember many passwords. You need a password for your e-mail account, your website's FTP password, online passwords (like website member account), etc. etc. etc. The list is endless. Also, you **should use different passwords for each account**. Because if you use only one password everywhere and someone gets this password you have a problem... A serious problem. The thief would have access to your e-mail account, website, etc. Unimaginable.
|
||||
|
||||
KeePass DX is a **free open source password manager for Android**, which helps you to **manage your passwords in a secure way**. You can put all your passwords in one database, which is locked with one **master key** or a **key file**. So you **only have to remember one single master password or select the key file** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
|
||||
KeePassDX is a **free open source password manager for Android**, which helps you to **manage your passwords in a secure way**. You can put all your passwords in one database, which is locked with one **master key** or a **key file**. So you **only have to remember one single master password or select the key file** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
|
||||
|
||||
## Is it really free?
|
||||
|
||||
Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
|
||||
Yes, KeePassDX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
|
||||
|
||||
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
|
||||
|
||||
@@ -38,7 +39,7 @@ You can contribute in different ways to help us on our work.
|
||||
* Add features by a **[pull request](https://help.github.com/articles/about-pull-requests/)**.
|
||||
* Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** into your language (By using [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or with a manual [pull request](https://help.github.com/articles/about-pull-requests/))
|
||||
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
|
||||
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePass DX
|
||||
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePassDX
|
||||
|
||||
## Download
|
||||
|
||||
@@ -54,7 +55,7 @@ You can contribute in different ways to help us on our work.
|
||||
|
||||
## F.A.Q.
|
||||
|
||||
Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
|
||||
Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassDX/wiki/F.A.Q.)
|
||||
|
||||
## Other devices
|
||||
|
||||
@@ -64,21 +65,21 @@ Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
|
||||
Copyright (c) 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
|
||||
|
||||
This file is part of KeePass DX.
|
||||
This file is part of KeePassDX.
|
||||
|
||||
[KeePass DX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
|
||||
[KeePassDX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KeePass DX is distributed in the hope that it will be useful,
|
||||
KeePassDX is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
|
||||
|
||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.cxx
|
||||
.externalNativeBuild
|
||||
|
||||
@@ -4,15 +4,15 @@ apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 27
|
||||
versionCode = 19
|
||||
versionName = "2.5.0.0beta19"
|
||||
targetSdkVersion 28
|
||||
versionCode = 26
|
||||
versionName = "2.5.0.0beta26"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
@@ -27,7 +27,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled = false
|
||||
@@ -79,42 +78,35 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
def supportVersion = "27.1.1"
|
||||
def spongycastleVersion = "1.58.0.0"
|
||||
def permissionDispatcherVersion = "3.3.1"
|
||||
def room_version = "2.2.1"
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "com.android.support:appcompat-v7:$supportVersion"
|
||||
implementation "com.android.support:design:$supportVersion"
|
||||
implementation "com.android.support:preference-v7:$supportVersion"
|
||||
implementation "com.android.support:preference-v14:$supportVersion"
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.biometric:biometric:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
||||
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
||||
// Expandable view
|
||||
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
|
||||
// Time
|
||||
implementation 'joda-time:joda-time:2.9.9'
|
||||
implementation 'org.sufficientlysecure:html-textview:3.5'
|
||||
implementation 'com.nononsenseapps:filepicker:4.1.0'
|
||||
// Color
|
||||
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
|
||||
// Education
|
||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
|
||||
// Permissions
|
||||
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
||||
// if you don't use android.app.Fragment you can exclude support for them
|
||||
exclude module: "support-v13"
|
||||
}
|
||||
kapt "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
|
||||
// Apache Commons Collections
|
||||
implementation 'commons-collections:commons-collections:3.2.1'
|
||||
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||
// Base64
|
||||
implementation 'biz.source_code:base64coder:2010-12-19'
|
||||
// IO-Extras
|
||||
implementation 'com.github.davidmoten:io-extras:0.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.4'
|
||||
implementation 'com.google.guava:guava:23.0-android'
|
||||
// Apache Commons Codec
|
||||
implementation 'commons-codec:commons-codec:1.11'
|
||||
// Icon pack
|
||||
implementation project(path: ':icon-pack-classic')
|
||||
implementation project(path: ':icon-pack-material')
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.kunzisoft.keepass.tests.database.TestData;
|
||||
|
||||
public class AccentTest extends AndroidTestCase {
|
||||
|
||||
private static final String KEYFILE = "";
|
||||
private static final String PASSWORD = "é";
|
||||
private static final String ASSET = "accent.kdb";
|
||||
private static final String FILENAME = "/sdcard/accent.kdb";
|
||||
|
||||
public void testOpen() {
|
||||
|
||||
/*
|
||||
try {
|
||||
TestData.GetDb(getContext(), ASSET, PASSWORD, KEYFILE, FILENAME);
|
||||
} catch (Exception e) {
|
||||
assertTrue("Failed to open database", false);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import android.test.suitebuilder.TestSuiteBuilder;
|
||||
|
||||
public class AllTests extends TestSuite {
|
||||
|
||||
public static Test suite() {
|
||||
return new TestSuiteBuilder(AllTests.class)
|
||||
.includeAllPackagesUnderHere()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import android.test.suitebuilder.TestSuiteBuilder;
|
||||
|
||||
public class OutputTests extends TestSuite {
|
||||
|
||||
public static Test suite() {
|
||||
|
||||
return new TestSuiteBuilder(AllTests.class)
|
||||
.includePackages("com.kunzisoft.keepass.tests.output")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwDate
|
||||
import org.junit.Assert
|
||||
|
||||
class PwDateTest : TestCase() {
|
||||
|
||||
fun testDate() {
|
||||
val jDate = PwDate(System.currentTimeMillis())
|
||||
val intermediate = PwDate(jDate)
|
||||
val cDate = PwDate(intermediate.byteArrayDate!!, 0)
|
||||
|
||||
Assert.assertTrue("jDate and intermediate not equal", jDate == intermediate)
|
||||
Assert.assertTrue("jDate and cDate not equal", cDate == jDate)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Calendar;
|
||||
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwEntryV3;
|
||||
import com.kunzisoft.keepass.tests.database.TestData;
|
||||
|
||||
public class PwEntryTestV3 extends AndroidTestCase {
|
||||
PwEntryV3 mPE;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// mPE = (PwEntryV3) TestData.GetTest1(getContext()).getEntryAt(0);
|
||||
|
||||
}
|
||||
|
||||
public void testName() {
|
||||
assertTrue("Name was " + mPE.getTitle(), mPE.getTitle().equals("Amazon"));
|
||||
}
|
||||
|
||||
public void testPassword() throws UnsupportedEncodingException {
|
||||
String sPass = "12345";
|
||||
byte[] password = sPass.getBytes("UTF-8");
|
||||
|
||||
assertArrayEquals(password, mPE.getPasswordBytes());
|
||||
}
|
||||
|
||||
public void testCreation() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(mPE.getCreationTime().getDate());
|
||||
|
||||
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
|
||||
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);
|
||||
assertEquals("Incorrect day.", cal.get(Calendar.DAY_OF_MONTH), 23);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class PwEntryTestV4 extends TestCase {
|
||||
public void testAssign() {
|
||||
/*
|
||||
TODO Test
|
||||
PwEntryV4 entry = new PwEntryV4();
|
||||
|
||||
entry.setAdditional("test223");
|
||||
|
||||
entry.setAutoType(new AutoType());
|
||||
entry.getAutoType().defaultSequence = "1324";
|
||||
entry.getAutoType().enabled = true;
|
||||
entry.getAutoType().obfuscationOptions = 123412432109L;
|
||||
entry.getAutoType().put("key", "value");
|
||||
|
||||
entry.setBackgroundColor("blue");
|
||||
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
|
||||
entry.setIconCustom(new PwIconCustom(UUID.randomUUID(), new byte[0]));
|
||||
entry.setForegroundColor("red");
|
||||
entry.addToHistory(new PwEntryV4());
|
||||
entry.setIconStandard(new PwIconStandard(5));
|
||||
entry.setOverrideURL("override");
|
||||
entry.setParent(new PwGroupV4());
|
||||
entry.addExtraField("key2", new ProtectedString(false, "value2"));
|
||||
entry.setUrl("http://localhost");
|
||||
entry.setNodeId(UUID.randomUUID());
|
||||
|
||||
PwEntryV4 target = new PwEntryV4();
|
||||
target.updateWith(entry);
|
||||
|
||||
/* This test is not so useful now that I am not implementing value equality for Entries
|
||||
assertTrue("Entries do not match.", entry.equals(target));
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwGroupV3;
|
||||
import com.kunzisoft.keepass.tests.database.TestData;
|
||||
|
||||
public class PwGroupTest extends AndroidTestCase {
|
||||
|
||||
PwGroupV3 mPG;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
//mPG = (PwGroupV3) TestData.GetTest1(getContext()).getGroups().get(0);
|
||||
|
||||
}
|
||||
|
||||
public void testGroupName() {
|
||||
//assertTrue("Name was " + mPG.getTitle(), mPG.getTitle().equals("Internet"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests
|
||||
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
|
||||
import com.kunzisoft.keepass.stream.*
|
||||
import junit.framework.TestCase
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.*
|
||||
|
||||
class StringDatabaseKDBUtilsTest : TestCase() {
|
||||
|
||||
fun testReadWriteLongZero() {
|
||||
testReadWriteLong(0.toByte())
|
||||
}
|
||||
|
||||
fun testReadWriteLongMax() {
|
||||
testReadWriteLong(java.lang.Byte.MAX_VALUE)
|
||||
}
|
||||
|
||||
fun testReadWriteLongMin() {
|
||||
testReadWriteLong(java.lang.Byte.MIN_VALUE)
|
||||
}
|
||||
|
||||
fun testReadWriteLongRnd() {
|
||||
val rnd = Random()
|
||||
val buf = ByteArray(1)
|
||||
rnd.nextBytes(buf)
|
||||
|
||||
testReadWriteLong(buf[0])
|
||||
}
|
||||
|
||||
private fun testReadWriteLong(value: Byte) {
|
||||
val orig = ByteArray(8)
|
||||
setArray(orig, value, 8)
|
||||
|
||||
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
|
||||
}
|
||||
|
||||
fun testReadWriteIntZero() {
|
||||
testReadWriteInt(0.toByte())
|
||||
}
|
||||
|
||||
fun testReadWriteIntMin() {
|
||||
testReadWriteInt(java.lang.Byte.MIN_VALUE)
|
||||
}
|
||||
|
||||
fun testReadWriteIntMax() {
|
||||
testReadWriteInt(java.lang.Byte.MAX_VALUE)
|
||||
}
|
||||
|
||||
private fun testReadWriteInt(value: Byte) {
|
||||
val orig = ByteArray(4)
|
||||
|
||||
for (i in 0..3) {
|
||||
orig[i] = 0
|
||||
}
|
||||
|
||||
setArray(orig, value, 4)
|
||||
|
||||
val one = bytes4ToInt(orig)
|
||||
val dest = intTo4Bytes(one)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
|
||||
}
|
||||
|
||||
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
||||
for (i in 0 until size) {
|
||||
buf[i] = value
|
||||
}
|
||||
}
|
||||
|
||||
fun testReadWriteShortOne() {
|
||||
val orig = ByteArray(2)
|
||||
|
||||
orig[0] = 0
|
||||
orig[1] = 1
|
||||
|
||||
val one = bytes2ToUShort(orig)
|
||||
val dest = uShortTo2Bytes(one)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
}
|
||||
|
||||
fun testReadWriteShortMin() {
|
||||
testReadWriteShort(java.lang.Byte.MIN_VALUE)
|
||||
}
|
||||
|
||||
fun testReadWriteShortMax() {
|
||||
testReadWriteShort(java.lang.Byte.MAX_VALUE)
|
||||
}
|
||||
|
||||
private fun testReadWriteShort(value: Byte) {
|
||||
val orig = ByteArray(2)
|
||||
setArray(orig, value, 2)
|
||||
|
||||
val one = bytes2ToUShort(orig)
|
||||
val dest = uShortTo2Bytes(one)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
}
|
||||
|
||||
fun testReadWriteByteZero() {
|
||||
testReadWriteByte(0.toByte())
|
||||
}
|
||||
|
||||
fun testReadWriteByteMin() {
|
||||
testReadWriteByte(java.lang.Byte.MIN_VALUE)
|
||||
}
|
||||
|
||||
fun testReadWriteByteMax() {
|
||||
testReadWriteShort(java.lang.Byte.MAX_VALUE)
|
||||
}
|
||||
|
||||
private fun testReadWriteByte(value: Byte) {
|
||||
val dest: Byte = uIntToByte(byteToUInt(value))
|
||||
assert(value == dest)
|
||||
}
|
||||
|
||||
fun testDate() {
|
||||
val cal = Calendar.getInstance()
|
||||
|
||||
val expected = Calendar.getInstance()
|
||||
expected.set(2008, 1, 2, 3, 4, 5)
|
||||
|
||||
val actual = Calendar.getInstance()
|
||||
dateTo5Bytes(expected.time, cal)?.let { buf ->
|
||||
actual.time = bytes5ToDate(buf, cal).date
|
||||
}
|
||||
|
||||
val jDate = DateInstant(System.currentTimeMillis())
|
||||
val intermediate = DateInstant(jDate)
|
||||
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!)
|
||||
|
||||
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
|
||||
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
|
||||
assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
|
||||
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
|
||||
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
|
||||
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
|
||||
assertTrue("jDate and intermediate not equal", jDate == intermediate)
|
||||
assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
|
||||
}
|
||||
|
||||
fun testUUID() {
|
||||
val bUUID = ByteArray(16)
|
||||
Random().nextBytes(bUUID)
|
||||
|
||||
val uuid = bytes16ToUuid(bUUID)
|
||||
val eUUID = uuidTo16Bytes(uuid)
|
||||
|
||||
val lUUID = bytes16ToUuid(bUUID)
|
||||
val leUUID = uuidTo16Bytes(lUUID)
|
||||
|
||||
assertArrayEquals("UUID match failed", bUUID, eUUID)
|
||||
assertArrayEquals("UUID match failed", bUUID, leUUID)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun testULongMax() {
|
||||
val ulongBytes = ByteArray(8)
|
||||
for (i in ulongBytes.indices) {
|
||||
ulongBytes[i] = -1
|
||||
}
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
val leos = LittleEndianDataOutputStream(bos)
|
||||
leos.writeLong(ULONG_MAX_VALUE)
|
||||
leos.close()
|
||||
|
||||
val uLongMax = bos.toByteArray()
|
||||
|
||||
assertArrayEquals(ulongBytes, uLongMax)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class TestUtil {
|
||||
private static final File sdcard = Environment.getExternalStorageDirectory();
|
||||
|
||||
public static void extractKey(Context ctx, String asset, String target) throws Exception {
|
||||
|
||||
InputStream key = ctx.getAssets().open(asset, AssetManager.ACCESS_STREAMING);
|
||||
|
||||
FileOutputStream keyFile = new FileOutputStream(target);
|
||||
while (true) {
|
||||
byte[] buf = new byte[1024];
|
||||
int read = key.read(buf);
|
||||
if ( read == -1 ) {
|
||||
break;
|
||||
} else {
|
||||
keyFile.write(buf, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
keyFile.close();
|
||||
|
||||
}
|
||||
|
||||
public static String getSdPath(String filename) {
|
||||
File file = new File(sdcard, filename);
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwDate;
|
||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
||||
import com.kunzisoft.keepass.stream.LEDataOutputStream;
|
||||
import com.kunzisoft.keepass.utils.Types;
|
||||
|
||||
public class TypesTest extends TestCase {
|
||||
|
||||
public void testReadWriteLongZero() {
|
||||
testReadWriteLong((byte) 0);
|
||||
}
|
||||
|
||||
public void testReadWriteLongMax() {
|
||||
testReadWriteLong(Byte.MAX_VALUE);
|
||||
}
|
||||
|
||||
public void testReadWriteLongMin() {
|
||||
testReadWriteLong(Byte.MIN_VALUE);
|
||||
}
|
||||
|
||||
public void testReadWriteLongRnd() {
|
||||
Random rnd = new Random();
|
||||
byte[] buf = new byte[1];
|
||||
rnd.nextBytes(buf);
|
||||
|
||||
testReadWriteLong(buf[0]);
|
||||
}
|
||||
|
||||
private void testReadWriteLong(byte value) {
|
||||
byte[] orig = new byte[8];
|
||||
byte[] dest = new byte[8];
|
||||
|
||||
setArray(orig, value, 0, 8);
|
||||
|
||||
long one = LEDataInputStream.readLong(orig, 0);
|
||||
LEDataOutputStream.writeLong(one, dest, 0);
|
||||
|
||||
assertArrayEquals(orig, dest);
|
||||
|
||||
}
|
||||
|
||||
public void testReadWriteIntZero() {
|
||||
testReadWriteInt((byte) 0);
|
||||
}
|
||||
|
||||
public void testReadWriteIntMin() {
|
||||
testReadWriteInt(Byte.MIN_VALUE);
|
||||
}
|
||||
|
||||
public void testReadWriteIntMax() {
|
||||
testReadWriteInt(Byte.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void testReadWriteInt(byte value) {
|
||||
byte[] orig = new byte[4];
|
||||
byte[] dest = new byte[4];
|
||||
|
||||
for (int i = 0; i < 4; i++ ) {
|
||||
orig[i] = 0;
|
||||
}
|
||||
|
||||
setArray(orig, value, 0, 4);
|
||||
|
||||
int one = LEDataInputStream.readInt(orig, 0);
|
||||
|
||||
LEDataOutputStream.writeInt(one, dest, 0);
|
||||
|
||||
assertArrayEquals(orig, dest);
|
||||
|
||||
}
|
||||
|
||||
private void setArray(byte[] buf, byte value, int offset, int size) {
|
||||
for (int i = offset; i < offset + size; i++) {
|
||||
buf[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadWriteShortOne() {
|
||||
byte[] orig = new byte[2];
|
||||
byte[] dest = new byte[2];
|
||||
|
||||
orig[0] = 0;
|
||||
orig[1] = 1;
|
||||
|
||||
int one = LEDataInputStream.readUShort(orig, 0);
|
||||
dest = LEDataOutputStream.writeUShortBuf(one);
|
||||
|
||||
assertArrayEquals(orig, dest);
|
||||
|
||||
}
|
||||
|
||||
public void testReadWriteShortMin() {
|
||||
testReadWriteShort(Byte.MIN_VALUE);
|
||||
}
|
||||
|
||||
public void testReadWriteShortMax() {
|
||||
testReadWriteShort(Byte.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void testReadWriteShort(byte value) {
|
||||
byte[] orig = new byte[2];
|
||||
byte[] dest = new byte[2];
|
||||
|
||||
setArray(orig, value, 0, 2);
|
||||
|
||||
int one = LEDataInputStream.readUShort(orig, 0);
|
||||
LEDataOutputStream.writeUShort(one, dest, 0);
|
||||
|
||||
assertArrayEquals(orig, dest);
|
||||
|
||||
}
|
||||
|
||||
public void testReadWriteByteZero() {
|
||||
testReadWriteByte((byte) 0);
|
||||
}
|
||||
|
||||
public void testReadWriteByteMin() {
|
||||
testReadWriteByte(Byte.MIN_VALUE);
|
||||
}
|
||||
|
||||
public void testReadWriteByteMax() {
|
||||
testReadWriteShort(Byte.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void testReadWriteByte(byte value) {
|
||||
byte[] orig = new byte[1];
|
||||
byte[] dest = new byte[1];
|
||||
|
||||
setArray(orig, value, 0, 1);
|
||||
|
||||
int one = Types.readUByte(orig, 0);
|
||||
Types.writeUByte(one, dest, 0);
|
||||
|
||||
assertArrayEquals(orig, dest);
|
||||
|
||||
}
|
||||
|
||||
public void testDate() {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
|
||||
Calendar expected = Calendar.getInstance();
|
||||
expected.set(2008, 1, 2, 3, 4, 5);
|
||||
|
||||
byte[] buf = PwDate.Companion.writeTime(expected.getTime(), cal);
|
||||
Calendar actual = Calendar.getInstance();
|
||||
actual.setTime(PwDate.Companion.readTime(buf, 0, cal));
|
||||
|
||||
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR));
|
||||
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH));
|
||||
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH));
|
||||
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY));
|
||||
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE));
|
||||
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND));
|
||||
}
|
||||
|
||||
public void testUUID() {
|
||||
Random rnd = new Random();
|
||||
byte[] bUUID = new byte[16];
|
||||
rnd.nextBytes(bUUID);
|
||||
|
||||
UUID uuid = Types.bytestoUUID(bUUID);
|
||||
byte[] eUUID = Types.UUIDtoBytes(uuid);
|
||||
|
||||
assertArrayEquals("UUID match failed", bUUID, eUUID);
|
||||
}
|
||||
|
||||
public void testULongMax() throws Exception {
|
||||
byte[] ulongBytes = new byte[8];
|
||||
for (int i = 0; i < ulongBytes.length; i++) {
|
||||
ulongBytes[i] = -1;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
LEDataOutputStream leos = new LEDataOutputStream(bos);
|
||||
leos.writeLong(Types.ULONG_MAX_VALUE);
|
||||
leos.close();
|
||||
|
||||
byte[] uLongMax = bos.toByteArray();
|
||||
|
||||
assertArrayEquals(ulongBytes, uLongMax);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.crypto;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
public class AESTest extends TestCase {
|
||||
|
||||
private Random mRand = new Random();
|
||||
|
||||
public void testEncrypt() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||
// Test above below and at the blocksize
|
||||
testFinal(15);
|
||||
testFinal(16);
|
||||
testFinal(17);
|
||||
|
||||
// Test random larger sizes
|
||||
int size = mRand.nextInt(494) + 18;
|
||||
testFinal(size);
|
||||
}
|
||||
|
||||
private void testFinal(int dataSize) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
|
||||
// Generate some input
|
||||
byte[] input = new byte[dataSize];
|
||||
mRand.nextBytes(input);
|
||||
|
||||
// Generate key
|
||||
byte[] keyArray = new byte[32];
|
||||
mRand.nextBytes(keyArray);
|
||||
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
|
||||
|
||||
// Generate IV
|
||||
byte[] ivArray = new byte[16];
|
||||
mRand.nextBytes(ivArray);
|
||||
IvParameterSpec iv = new IvParameterSpec(ivArray);
|
||||
|
||||
Cipher android = CipherFactory.INSTANCE.getInstance("AES/CBC/PKCS5Padding", true);
|
||||
android.init(Cipher.ENCRYPT_MODE, key, iv);
|
||||
byte[] outAndroid = android.doFinal(input, 0, dataSize);
|
||||
|
||||
Cipher nat = CipherFactory.INSTANCE.getInstance("AES/CBC/PKCS5Padding");
|
||||
nat.init(Cipher.ENCRYPT_MODE, key, iv);
|
||||
byte[] outNative = nat.doFinal(input, 0, dataSize);
|
||||
|
||||
assertArrayEquals("Arrays differ on size: " + dataSize, outAndroid, outNative);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.crypto
|
||||
|
||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.Random
|
||||
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
|
||||
class AESTest : TestCase() {
|
||||
|
||||
private val mRand = Random()
|
||||
|
||||
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidAlgorithmParameterException::class)
|
||||
fun testEncrypt() {
|
||||
// Test above below and at the blocksize
|
||||
testFinal(15)
|
||||
testFinal(16)
|
||||
testFinal(17)
|
||||
|
||||
// Test random larger sizes
|
||||
val size = mRand.nextInt(494) + 18
|
||||
testFinal(size)
|
||||
}
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
|
||||
private fun testFinal(dataSize: Int) {
|
||||
|
||||
// Generate some input
|
||||
val input = ByteArray(dataSize)
|
||||
mRand.nextBytes(input)
|
||||
|
||||
// Generate key
|
||||
val keyArray = ByteArray(32)
|
||||
mRand.nextBytes(keyArray)
|
||||
val key = SecretKeySpec(keyArray, "AES")
|
||||
|
||||
// Generate IV
|
||||
val ivArray = ByteArray(16)
|
||||
mRand.nextBytes(ivArray)
|
||||
val iv = IvParameterSpec(ivArray)
|
||||
|
||||
val android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true)
|
||||
android.init(Cipher.ENCRYPT_MODE, key, iv)
|
||||
val outAndroid = android.doFinal(input, 0, dataSize)
|
||||
|
||||
val nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding")
|
||||
nat.init(Cipher.ENCRYPT_MODE, key, iv)
|
||||
val outNative = nat.doFinal(input, 0, dataSize)
|
||||
|
||||
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.crypto;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
||||
import com.kunzisoft.keepass.crypto.engine.AesEngine;
|
||||
import com.kunzisoft.keepass.crypto.engine.CipherEngine;
|
||||
import com.kunzisoft.keepass.stream.BetterCipherInputStream;
|
||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
||||
|
||||
public class CipherTest extends TestCase {
|
||||
private Random rand = new Random();
|
||||
|
||||
public void testCipherFactory() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
byte[] plaintext = new byte[1024];
|
||||
|
||||
rand.nextBytes(key);
|
||||
rand.nextBytes(iv);
|
||||
rand.nextBytes(plaintext);
|
||||
|
||||
CipherEngine aes = CipherFactory.INSTANCE.getInstance(AesEngine.CIPHER_UUID);
|
||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||
|
||||
byte[] secrettext = encrypt.doFinal(plaintext);
|
||||
byte[] decrypttext = decrypt.doFinal(secrettext);
|
||||
|
||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
||||
}
|
||||
|
||||
public void testCipherStreams() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {
|
||||
final int MESSAGE_LENGTH = 1024;
|
||||
|
||||
byte[] key = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
byte[] plaintext = new byte[MESSAGE_LENGTH];
|
||||
|
||||
rand.nextBytes(key);
|
||||
rand.nextBytes(iv);
|
||||
rand.nextBytes(plaintext);
|
||||
|
||||
CipherEngine aes = CipherFactory.INSTANCE.getInstance(AesEngine.CIPHER_UUID);
|
||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
CipherOutputStream cos = new CipherOutputStream(bos, encrypt);
|
||||
cos.write(plaintext);
|
||||
cos.close();
|
||||
|
||||
byte[] secrettext = bos.toByteArray();
|
||||
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(secrettext);
|
||||
BetterCipherInputStream cis = new BetterCipherInputStream(bis, decrypt);
|
||||
LEDataInputStream lis = new LEDataInputStream(cis);
|
||||
|
||||
byte[] decrypttext = lis.readBytes(MESSAGE_LENGTH);
|
||||
|
||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.crypto
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.Random
|
||||
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.IllegalBlockSizeException
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||
import com.kunzisoft.keepass.crypto.engine.AesEngine
|
||||
import com.kunzisoft.keepass.stream.BetterCipherInputStream
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
|
||||
|
||||
class CipherTest : TestCase() {
|
||||
private val rand = Random()
|
||||
|
||||
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class)
|
||||
fun testCipherFactory() {
|
||||
val key = ByteArray(32)
|
||||
val iv = ByteArray(16)
|
||||
|
||||
val plaintext = ByteArray(1024)
|
||||
|
||||
rand.nextBytes(key)
|
||||
rand.nextBytes(iv)
|
||||
rand.nextBytes(plaintext)
|
||||
|
||||
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
|
||||
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
|
||||
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
|
||||
|
||||
val secrettext = encrypt.doFinal(plaintext)
|
||||
val decrypttext = decrypt.doFinal(secrettext)
|
||||
|
||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
|
||||
}
|
||||
|
||||
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class, IOException::class)
|
||||
fun testCipherStreams() {
|
||||
val MESSAGE_LENGTH = 1024
|
||||
|
||||
val key = ByteArray(32)
|
||||
val iv = ByteArray(16)
|
||||
|
||||
val plaintext = ByteArray(MESSAGE_LENGTH)
|
||||
|
||||
rand.nextBytes(key)
|
||||
rand.nextBytes(iv)
|
||||
rand.nextBytes(plaintext)
|
||||
|
||||
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID)
|
||||
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
|
||||
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
val cos = CipherOutputStream(bos, encrypt)
|
||||
cos.write(plaintext)
|
||||
cos.close()
|
||||
|
||||
val secrettext = bos.toByteArray()
|
||||
|
||||
val bis = ByteArrayInputStream(secrettext)
|
||||
val cis = BetterCipherInputStream(bis, decrypt)
|
||||
val lis = LittleEndianDataInputStream(cis)
|
||||
|
||||
val decrypttext = lis.readBytes(MESSAGE_LENGTH)
|
||||
|
||||
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.crypto;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey;
|
||||
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey;
|
||||
|
||||
public class FinalKeyTest extends TestCase {
|
||||
private Random mRand;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mRand = new Random();
|
||||
}
|
||||
|
||||
public void testNativeAndroid() throws IOException {
|
||||
// Test both an old and an even number to test my flip variable
|
||||
testNativeFinalKey(5);
|
||||
testNativeFinalKey(6);
|
||||
}
|
||||
|
||||
private void testNativeFinalKey(int rounds) throws IOException {
|
||||
byte[] seed = new byte[32];
|
||||
byte[] key = new byte[32];
|
||||
byte[] nativeKey;
|
||||
byte[] androidKey;
|
||||
|
||||
mRand.nextBytes(seed);
|
||||
mRand.nextBytes(key);
|
||||
|
||||
AndroidFinalKey aKey = new AndroidFinalKey();
|
||||
androidKey = aKey.transformMasterKey(seed, key, rounds);
|
||||
|
||||
NativeFinalKey nKey = new NativeFinalKey();
|
||||
nativeKey = nKey.transformMasterKey(seed, key, rounds);
|
||||
|
||||
assertArrayEquals("Does not match", androidKey, nativeKey);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.crypto
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
|
||||
import java.io.IOException
|
||||
import java.util.Random
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey
|
||||
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey
|
||||
|
||||
class FinalKeyTest : TestCase() {
|
||||
private var mRand: Random? = null
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
|
||||
mRand = Random()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun testNativeAndroid() {
|
||||
// Test both an old and an even number to test my flip variable
|
||||
testNativeFinalKey(5)
|
||||
testNativeFinalKey(6)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun testNativeFinalKey(rounds: Int) {
|
||||
val seed = ByteArray(32)
|
||||
val key = ByteArray(32)
|
||||
val nativeKey: ByteArray
|
||||
val androidKey: ByteArray
|
||||
|
||||
mRand!!.nextBytes(seed)
|
||||
mRand!!.nextBytes(key)
|
||||
|
||||
val aKey = AndroidFinalKey()
|
||||
androidKey = aKey.transformMasterKey(seed, key, rounds.toLong())
|
||||
|
||||
val nKey = NativeFinalKey()
|
||||
nativeKey = nKey.transformMasterKey(seed, key, rounds.toLong())
|
||||
|
||||
assertArrayEquals("Does not match", androidKey, nativeKey)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned;
|
||||
import com.kunzisoft.keepass.database.element.PwDatabase;
|
||||
import com.kunzisoft.keepass.database.element.PwDatabaseV3;
|
||||
import com.kunzisoft.keepass.database.element.PwEntryV3;
|
||||
|
||||
public class DeleteEntry extends AndroidTestCase {
|
||||
private static final String GROUP1_NAME = "Group1";
|
||||
private static final String ENTRY1_NAME = "Test1";
|
||||
private static final String ENTRY2_NAME = "Test2";
|
||||
private static final String KEYFILE = "";
|
||||
private static final String PASSWORD = "12345";
|
||||
private static final String ASSET = "delete.kdb";
|
||||
private static final String FILENAME = "/sdcard/delete.kdb";
|
||||
|
||||
public void testDelete() {
|
||||
|
||||
/*
|
||||
Database db;
|
||||
|
||||
Context ctx = getContext();
|
||||
|
||||
try {
|
||||
db = TestData.GetDb(ctx, ASSET, PASSWORD, KEYFILE, FILENAME);
|
||||
} catch (Exception e) {
|
||||
assertTrue("Failed to open database: " + e.getMessage(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
PwDatabaseV3 pm = (PwDatabaseV3) db.getPwDatabase();
|
||||
GroupVersioned group1 = getGroup(pm, GROUP1_NAME);
|
||||
assertNotNull("Could not find group1", group1);
|
||||
|
||||
// Delete the group
|
||||
DeleteGroupRunnable task = new DeleteGroupRunnable(null, db, group1, null, true);
|
||||
task.run();
|
||||
|
||||
// Verify the entries were deleted
|
||||
PwEntryInterface entry1 = getEntry(pm, ENTRY1_NAME);
|
||||
assertNull("Entry 1 was not removed", entry1);
|
||||
|
||||
PwEntryInterface entry2 = getEntry(pm, ENTRY2_NAME);
|
||||
assertNull("Entry 2 was not removed", entry2);
|
||||
|
||||
// Verify the entries were removed from the search index
|
||||
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
|
||||
GroupVersioned results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
|
||||
GroupVersioned results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
|
||||
|
||||
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
|
||||
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
|
||||
|
||||
// Verify the group was deleted
|
||||
group1 = getGroup(pm, GROUP1_NAME);
|
||||
assertNull("Group 1 was not removed.", group1);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
private PwEntryV3 getEntry(PwDatabaseV3 pm, String name) {
|
||||
/*
|
||||
TODO test
|
||||
List<PwEntryV3> entries = pm.getEntries();
|
||||
for ( int i = 0; i < entries.size(); i++ ) {
|
||||
PwEntryV3 entry = entries.get(i);
|
||||
if ( entry.getTitle().equals(name) ) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
*/
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private GroupVersioned getGroup(PwDatabase pm, String name) {
|
||||
/*
|
||||
List<GroupVersioned> groups = pm.getGroups();
|
||||
for ( int i = 0; i < groups.size(); i++ ) {
|
||||
GroupVersioned group = groups.get(i);
|
||||
if ( group.getTitle().equals(name) ) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwDatabaseV4;
|
||||
import com.kunzisoft.keepass.database.element.PwEntryV4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class EntryV4 extends TestCase {
|
||||
|
||||
public void testBackup() {
|
||||
/*
|
||||
PwDatabaseV4 db = new PwDatabaseV4();
|
||||
|
||||
db.setHistoryMaxItems(2);
|
||||
|
||||
PwEntryV4 entry = new PwEntryV4();
|
||||
entry.startToManageFieldReferences(db);
|
||||
entry.setTitle("Title1");
|
||||
entry.setUsername("User1");
|
||||
entry.createBackup(db);
|
||||
|
||||
entry.setTitle("Title2");
|
||||
entry.setUsername("User2");
|
||||
entry.createBackup(db);
|
||||
|
||||
entry.setTitle("Title3");
|
||||
entry.setUsername("User3");
|
||||
entry.createBackup(db);
|
||||
|
||||
PwEntryV4 backup = entry.getHistory().get(0);
|
||||
entry.stopToManageFieldReferences();
|
||||
assertEquals("Title2", backup.getTitle());
|
||||
assertEquals("User2", backup.getUsername());
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class Kdb3 extends AndroidTestCase {
|
||||
|
||||
private void testKeyfile(String dbAsset, String keyAsset, String password) throws Exception {
|
||||
/*
|
||||
Context ctx = getContext();
|
||||
|
||||
File sdcard = Environment.getExternalStorageDirectory();
|
||||
String keyPath = sdcard.getAbsolutePath() + "/key";
|
||||
|
||||
TestUtil.extractKey(ctx, keyAsset, keyPath);
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open(dbAsset, AssetManager.ACCESS_STREAMING);
|
||||
|
||||
ImporterV3 importer = new ImporterV3();
|
||||
importer.openDatabase(is, password, TestUtil.getKeyFileInputStream(ctx, keyPath));
|
||||
|
||||
is.close();
|
||||
*/
|
||||
}
|
||||
|
||||
public void testXMLKeyFile() throws Exception {
|
||||
testKeyfile("kdb_with_xml_keyfile.kdb", "keyfile.key", "12345");
|
||||
}
|
||||
|
||||
public void testBinary64KeyFile() throws Exception {
|
||||
testKeyfile("binary-key.kdb", "binary.key", "12345");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class Kdb3Twofish extends AndroidTestCase {
|
||||
public void testReadTwofish() throws Exception {
|
||||
/*
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("twofish.kdb", AssetManager.ACCESS_STREAMING);
|
||||
|
||||
ImporterV3 importer = new ImporterV3();
|
||||
|
||||
PwDatabaseV3 db = importer.openDatabase(is, "12345", null);
|
||||
|
||||
assertTrue(db.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish);
|
||||
|
||||
is.close();
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.kunzisoft.keepass.database.exception.InvalidDBException;
|
||||
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
||||
import com.kunzisoft.keepass.tests.TestUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class Kdb4 extends AndroidTestCase {
|
||||
|
||||
public void testDetection() throws IOException, InvalidDBException {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
|
||||
/*
|
||||
TODO Test
|
||||
Importer importer = ImporterFactory.createImporter(is);
|
||||
|
||||
assertTrue(importer instanceof ImporterV4);
|
||||
is.close();
|
||||
*/
|
||||
}
|
||||
|
||||
public void testParsing() throws IOException, InvalidDBException {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
importer.openDatabase(is, "12345", null);
|
||||
|
||||
is.close();
|
||||
*/
|
||||
}
|
||||
|
||||
public void testSavingKDBXV3() throws IOException, InvalidDBException, PwDbOutputException {
|
||||
testSaving("test.kdbx", "12345", "test-out.kdbx");
|
||||
}
|
||||
|
||||
public void testSavingKDBXV4() throws IOException, InvalidDBException, PwDbOutputException {
|
||||
testSaving("test-kdbxv4.kdbx", "1", "test-kdbxv4-out.kdbx");
|
||||
}
|
||||
|
||||
private void testSaving(String inputFile, String password, String outputFile) throws IOException, InvalidDBException, PwDbOutputException {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open(inputFile, AssetManager.ACCESS_STREAMING);
|
||||
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
PwDatabaseV4 db = importer.openDatabase(is, password, null);
|
||||
is.close();
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
PwDbV4Output output = (PwDbV4Output) PwDbOutput.getInstance(db, bos);
|
||||
output.output();
|
||||
|
||||
byte[] data = bos.toByteArray();
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath(outputFile), false);
|
||||
|
||||
InputStream bis = new ByteArrayInputStream(data);
|
||||
bis = new CopyInputStream(bis, fos);
|
||||
importer = new ImporterV4();
|
||||
db = importer.openDatabase(bis, password, null);
|
||||
bis.close();
|
||||
|
||||
fos.close();
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
TestUtil.extractKey(getContext(), "keyfile.key", TestUtil.getSdPath("key"));
|
||||
TestUtil.extractKey(getContext(), "binary.key", TestUtil.getSdPath("key-binary"));
|
||||
}
|
||||
|
||||
public void testComposite() throws IOException, InvalidDBException {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("keyfile.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
importer.openDatabase(is, "12345", TestUtil.getKeyFileInputStream(ctx, TestUtil.getSdPath("key")));
|
||||
|
||||
is.close();
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
public void testCompositeBinary() throws IOException, InvalidDBException {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("keyfile-binary.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
importer.openDatabase(is, "12345", TestUtil.getKeyFileInputStream(ctx,TestUtil.getSdPath("key-binary")));
|
||||
|
||||
is.close();
|
||||
*/
|
||||
}
|
||||
|
||||
public void testKeyfile() throws IOException, InvalidDBException {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("key-only.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
importer.openDatabase(is, "", TestUtil.getKeyFileInputStream(ctx, TestUtil.getSdPath("key")));
|
||||
|
||||
is.close();
|
||||
*/
|
||||
}
|
||||
|
||||
public void testNoGzip() throws IOException, InvalidDBException {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("no-encrypt.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
importer.openDatabase(is, "12345", null);
|
||||
|
||||
is.close();
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class Kdb4Header extends AndroidTestCase {
|
||||
public void testReadHeader() throws Exception {
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
|
||||
PwDatabaseV4 db = importer.openDatabase(is, "12345", null);
|
||||
|
||||
assertEquals(6000, db.getNumberKeyEncryptionRounds());
|
||||
|
||||
assertTrue(db.getDataCipher().equals(AesEngine.CIPHER_UUID));
|
||||
|
||||
is.close();
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwDatabaseV4;
|
||||
import com.kunzisoft.keepass.database.element.SprEngineV4;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class SprEngineTest extends AndroidTestCase {
|
||||
private PwDatabaseV4 db;
|
||||
private SprEngineV4 spr;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
Context ctx = getContext();
|
||||
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
||||
|
||||
/*
|
||||
TODO Test
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
db = importer.openDatabase(is, "12345", null);
|
||||
|
||||
is.close();
|
||||
|
||||
spr = new SprEngineV4();
|
||||
*/
|
||||
}
|
||||
|
||||
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
|
||||
private final String ENCODE_UUID = "IN7RkON49Ui1UZ2ddqmLcw==";
|
||||
private final String RESULT = "Password";
|
||||
public void testRefReplace() {
|
||||
/*
|
||||
TODO TEST
|
||||
UUID entryUUID = decodeUUID(ENCODE_UUID);
|
||||
|
||||
PwEntryV4 entry = (PwEntryV4) db.getEntryById(entryUUID);
|
||||
|
||||
|
||||
assertEquals(RESULT, spr.compile(REF, entry, db));
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import com.kunzisoft.keepass.database.element.Database;
|
||||
|
||||
public class TestData {
|
||||
private static final String TEST1_KEYFILE = "";
|
||||
private static final String TEST1_KDB = "test1.kdb";
|
||||
private static final String TEST1_PASSWORD = "12345";
|
||||
|
||||
private static Database mDb1;
|
||||
|
||||
/*
|
||||
|
||||
public static Database GetDb1(Context ctx) throws Exception {
|
||||
return GetDb1(ctx, false);
|
||||
}
|
||||
|
||||
public static Database GetDb1(Context ctx, boolean forceReload) throws Exception {
|
||||
if ( mDb1 == null || forceReload ) {
|
||||
mDb1 = GetDb(ctx, TEST1_KDB, TEST1_PASSWORD, TEST1_KEYFILE, "/sdcard/test1.kdb");
|
||||
}
|
||||
|
||||
return mDb1;
|
||||
}
|
||||
|
||||
public static Database GetDb(Context ctx, String asset, String password, String keyfile, String filename) throws Exception {
|
||||
AssetManager am = ctx.getAssets();
|
||||
InputStream is = am.open(asset, AssetManager.ACCESS_STREAMING);
|
||||
|
||||
Database Db = new Database();
|
||||
|
||||
InputStream keyIs = TestUtil.getKeyFileInputStream(ctx, keyfile);
|
||||
|
||||
Db.loadData(ctx, is, password, keyIs, Importer.DEBUG);
|
||||
Uri.Builder b = new Uri.Builder();
|
||||
|
||||
Db.setUri(b.scheme("file").path(filename).build());
|
||||
|
||||
return Db;
|
||||
|
||||
}
|
||||
|
||||
public static PwDatabaseV3Debug GetTest1(Context ctx) throws Exception {
|
||||
if ( mDb1 == null ) {
|
||||
GetDb1(ctx);
|
||||
}
|
||||
|
||||
//return (PwDatabaseV3Debug) mDb1.getPwDatabase();
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.output;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class PwManagerOutputTest extends AndroidTestCase {
|
||||
// PwDatabaseV3Debug mPM;
|
||||
|
||||
/*
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mPM = TestData.GetTest1(getContext());
|
||||
}
|
||||
|
||||
public void testPlainContent() throws IOException, PwDbOutputException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
PwDbV3Output pos = new PwDbV3OutputDebug(mPM, bos, true);
|
||||
pos.outputPlanGroupAndEntries(bos);
|
||||
|
||||
assertTrue("No output", bos.toByteArray().length > 0);
|
||||
assertArrayEquals("Group and entry output doesn't match.", mPM.getPostHeader(), bos.toByteArray());
|
||||
|
||||
}
|
||||
|
||||
public void testChecksum() throws NoSuchAlgorithmException, IOException, PwDbOutputException {
|
||||
//FileOutputStream fos = new FileOutputStream("/dev/null");
|
||||
NullOutputStream nos = new NullOutputStream();
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
DigestOutputStream dos = new DigestOutputStream(nos, md);
|
||||
|
||||
PwDbV3Output pos = new PwDbV3OutputDebug(mPM, dos, true);
|
||||
pos.outputPlanGroupAndEntries(dos);
|
||||
dos.close();
|
||||
|
||||
byte[] digest = md.digest();
|
||||
assertTrue("No output", digest.length > 0);
|
||||
assertArrayEquals("Hash of groups and entries failed.", mPM.getDbHeader().contentsHash, digest);
|
||||
}
|
||||
|
||||
private void assertHeadersEquals(PwDbHeaderV3 expected, PwDbHeaderV3 actual) {
|
||||
assertEquals("Flags unequal", expected.flags, actual.flags);
|
||||
assertEquals("Entries unequal", expected.numEntries, actual.numEntries);
|
||||
assertEquals("Groups unequal", expected.numGroups, actual.numGroups);
|
||||
assertEquals("Key Rounds unequal", expected.numKeyEncRounds, actual.numKeyEncRounds);
|
||||
assertEquals("Signature1 unequal", expected.signature1, actual.signature1);
|
||||
assertEquals("Signature2 unequal", expected.signature2, actual.signature2);
|
||||
assertTrue("Version incompatible", PwDbHeaderV3.compatibleHeaders(expected.version, actual.version));
|
||||
assertArrayEquals("Hash unequal", expected.contentsHash, actual.contentsHash);
|
||||
assertArrayEquals("IV unequal", expected.encryptionIV, actual.encryptionIV);
|
||||
assertArrayEquals("Seed unequal", expected.masterSeed, actual.masterSeed);
|
||||
assertArrayEquals("Seed2 unequal", expected.transformSeed, actual.transformSeed);
|
||||
}
|
||||
|
||||
public void testHeader() throws PwDbOutputException, IOException {
|
||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
||||
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
|
||||
PwDbHeaderV3 header = pActual.outputHeader(bActual);
|
||||
|
||||
ByteArrayOutputStream bExpected = new ByteArrayOutputStream();
|
||||
PwDbHeaderOutputV3 outExpected = new PwDbHeaderOutputV3(mPM.getDbHeader(), bExpected);
|
||||
outExpected.output();
|
||||
|
||||
assertHeadersEquals(mPM.getDbHeader(), header);
|
||||
assertTrue("No output", bActual.toByteArray().length > 0);
|
||||
assertArrayEquals("Header does not match.", bExpected.toByteArray(), bActual.toByteArray());
|
||||
}
|
||||
|
||||
public void testFinalKey() throws PwDbOutputException {
|
||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
||||
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
|
||||
PwDbHeader hActual = pActual.outputHeader(bActual);
|
||||
byte[] finalKey = pActual.getFinalKey(hActual);
|
||||
|
||||
assertArrayEquals("Keys mismatched", mPM.getFinalKey(), finalKey);
|
||||
|
||||
}
|
||||
|
||||
public void testFullWrite() throws IOException, PwDbOutputException {
|
||||
AssetManager am = getContext().getAssets();
|
||||
InputStream is = am.open("test1.kdb");
|
||||
|
||||
// Pull file into byte array (for streaming fun)
|
||||
ByteArrayOutputStream bExpected = new ByteArrayOutputStream();
|
||||
while (true) {
|
||||
int data = is.read();
|
||||
if ( data == -1 ) {
|
||||
break;
|
||||
}
|
||||
bExpected.write(data);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
||||
PwDbV3Output pActual = new PwDbV3OutputDebug(mPM, bActual, true);
|
||||
pActual.output();
|
||||
//pActual.close();
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath("test1_out.kdb"));
|
||||
fos.write(bActual.toByteArray());
|
||||
fos.close();
|
||||
assertArrayEquals("Databases do not match.", bExpected.toByteArray(), bActual.toByteArray());
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.search;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.test.AndroidTestCase;
|
||||
import com.kunzisoft.keepass.database.element.Database;
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned;
|
||||
|
||||
public class SearchTest extends AndroidTestCase {
|
||||
|
||||
private Database mDb;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
//mDb = TestData.GetDb1(getContext(), true);
|
||||
}
|
||||
|
||||
public void testSearch() {
|
||||
GroupVersioned results = mDb.search("Amazon");
|
||||
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
||||
|
||||
}
|
||||
|
||||
public void testBackupIncluded() {
|
||||
updateOmitSetting(false);
|
||||
GroupVersioned results = mDb.search("BackupOnly");
|
||||
|
||||
//assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
||||
}
|
||||
|
||||
public void testBackupExcluded() {
|
||||
updateOmitSetting(true);
|
||||
GroupVersioned results = mDb.search("BackupOnly");
|
||||
|
||||
//assertFalse("Search result found, but should not have been.", results.numbersOfChildEntries() > 0);
|
||||
}
|
||||
|
||||
private void updateOmitSetting(boolean setting) {
|
||||
Context ctx = getContext();
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
editor.putBoolean("settings_omitbackup_key", setting);
|
||||
editor.commit();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.stream;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.kunzisoft.keepass.stream.HashedBlockInputStream;
|
||||
import com.kunzisoft.keepass.stream.HashedBlockOutputStream;
|
||||
|
||||
public class HashedBlock extends TestCase {
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
public void testBlockAligned() throws IOException {
|
||||
testSize(1024, 1024);
|
||||
}
|
||||
|
||||
public void testOffset() throws IOException {
|
||||
testSize(1500, 1024);
|
||||
}
|
||||
|
||||
private void testSize(int blockSize, int bufferSize) throws IOException {
|
||||
byte[] orig = new byte[blockSize];
|
||||
|
||||
rand.nextBytes(orig);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
HashedBlockOutputStream output = new HashedBlockOutputStream(bos, bufferSize);
|
||||
output.write(orig);
|
||||
output.close();
|
||||
|
||||
byte[] encoded = bos.toByteArray();
|
||||
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
||||
HashedBlockInputStream input = new HashedBlockInputStream(bis);
|
||||
|
||||
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
|
||||
while ( true ) {
|
||||
byte[] buf = new byte[1024];
|
||||
int read = input.read(buf);
|
||||
if ( read == -1 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
decoded.write(buf, 0, read);
|
||||
}
|
||||
|
||||
byte[] out = decoded.toByteArray();
|
||||
|
||||
assertArrayEquals(orig, out);
|
||||
|
||||
}
|
||||
|
||||
public void testGZIPStream() throws IOException {
|
||||
final int testLength = 32000;
|
||||
|
||||
byte[] orig = new byte[testLength];
|
||||
rand.nextBytes(orig);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
HashedBlockOutputStream hos = new HashedBlockOutputStream(bos);
|
||||
GZIPOutputStream zos = new GZIPOutputStream(hos);
|
||||
|
||||
zos.write(orig);
|
||||
zos.close();
|
||||
|
||||
byte[] compressed = bos.toByteArray();
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
|
||||
HashedBlockInputStream his = new HashedBlockInputStream(bis);
|
||||
GZIPInputStream zis = new GZIPInputStream(his);
|
||||
|
||||
byte[] uncompressed = new byte[testLength];
|
||||
|
||||
int read = 0;
|
||||
while (read != -1 && testLength - read > 0) {
|
||||
read += zis.read(uncompressed, read, testLength - read);
|
||||
|
||||
}
|
||||
|
||||
assertArrayEquals("Output not equal to input", orig, uncompressed);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.stream
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.Random
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
||||
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
|
||||
|
||||
class HashedBlock : TestCase() {
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun testBlockAligned() {
|
||||
testSize(1024, 1024)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun testOffset() {
|
||||
testSize(1500, 1024)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun testSize(blockSize: Int, bufferSize: Int) {
|
||||
val orig = ByteArray(blockSize)
|
||||
|
||||
rand.nextBytes(orig)
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
val output = HashedBlockOutputStream(bos, bufferSize)
|
||||
output.write(orig)
|
||||
output.close()
|
||||
|
||||
val encoded = bos.toByteArray()
|
||||
|
||||
val bis = ByteArrayInputStream(encoded)
|
||||
val input = HashedBlockInputStream(bis)
|
||||
|
||||
val decoded = ByteArrayOutputStream()
|
||||
while (true) {
|
||||
val buf = ByteArray(1024)
|
||||
val read = input.read(buf)
|
||||
if (read == -1) {
|
||||
break
|
||||
}
|
||||
|
||||
decoded.write(buf, 0, read)
|
||||
}
|
||||
|
||||
val out = decoded.toByteArray()
|
||||
|
||||
assertArrayEquals(orig, out)
|
||||
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun testGZIPStream() {
|
||||
val testLength = 32000
|
||||
|
||||
val orig = ByteArray(testLength)
|
||||
rand.nextBytes(orig)
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
val hos = HashedBlockOutputStream(bos)
|
||||
val zos = GZIPOutputStream(hos)
|
||||
|
||||
zos.write(orig)
|
||||
zos.close()
|
||||
|
||||
val compressed = bos.toByteArray()
|
||||
val bis = ByteArrayInputStream(compressed)
|
||||
val his = HashedBlockInputStream(bis)
|
||||
val zis = GZIPInputStream(his)
|
||||
|
||||
val uncompressed = ByteArray(testLength)
|
||||
|
||||
var read = 0
|
||||
while (read != -1 && testLength - read > 0) {
|
||||
read += zis.read(uncompressed, read, testLength - read)
|
||||
|
||||
}
|
||||
|
||||
assertArrayEquals("Output not equal to input", orig, uncompressed)
|
||||
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val rand = Random()
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.kunzisoft.keepass.utils.StringUtil;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class StringUtilTest extends TestCase {
|
||||
private final String text = "AbCdEfGhIj";
|
||||
private final String search = "BcDe";
|
||||
private final String badSearch = "Ed";
|
||||
|
||||
public void testIndexOfIgnoreCase1() {
|
||||
assertEquals(1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH));
|
||||
}
|
||||
|
||||
public void testIndexOfIgnoreCase2() {
|
||||
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, search, Locale.ENGLISH), 2);
|
||||
}
|
||||
|
||||
public void testIndexOfIgnoreCase3() {
|
||||
assertEquals(-1, StringUtil.INSTANCE.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH));
|
||||
}
|
||||
|
||||
private final String repText = "AbCtestingaBc";
|
||||
private final String repSearch = "ABc";
|
||||
private final String repSearchBad = "CCCCCC";
|
||||
private final String repNew = "12345";
|
||||
private final String repResult = "12345testing12345";
|
||||
public void testReplaceAllIgnoresCase1() {
|
||||
assertEquals(repResult, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH));
|
||||
}
|
||||
|
||||
public void testReplaceAllIgnoresCase2() {
|
||||
assertEquals(repText, StringUtil.INSTANCE.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.utils
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
import com.kunzisoft.keepass.utils.StringUtil
|
||||
|
||||
import junit.framework.TestCase
|
||||
|
||||
class StringUtilTest : TestCase() {
|
||||
private val text = "AbCdEfGhIj"
|
||||
private val search = "BcDe"
|
||||
private val badSearch = "Ed"
|
||||
|
||||
private val repText = "AbCtestingaBc"
|
||||
private val repSearch = "ABc"
|
||||
private val repSearchBad = "CCCCCC"
|
||||
private val repNew = "12345"
|
||||
private val repResult = "12345testing12345"
|
||||
|
||||
fun testIndexOfIgnoreCase1() {
|
||||
assertEquals(1, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH))
|
||||
}
|
||||
|
||||
fun testIndexOfIgnoreCase2() {
|
||||
assertEquals(-1f, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH).toFloat(), 2f)
|
||||
}
|
||||
|
||||
fun testIndexOfIgnoreCase3() {
|
||||
assertEquals(-1, StringUtil.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH))
|
||||
}
|
||||
|
||||
fun testReplaceAllIgnoresCase1() {
|
||||
assertEquals(repResult, StringUtil.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH))
|
||||
}
|
||||
|
||||
fun testReplaceAllIgnoresCase2() {
|
||||
assertEquals(repText, StringUtil.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH))
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,10 @@
|
||||
android:normalScreens="true"
|
||||
android:largeScreens="true"
|
||||
android:anyDensity="true" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
@@ -20,7 +21,10 @@
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup"
|
||||
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
||||
android:theme="@style/KeepassDXStyle.Night">
|
||||
android:largeHeap="true"
|
||||
android:resizeableActivity="true"
|
||||
android:theme="@style/KeepassDXStyle.Night"
|
||||
tools:targetApi="n">
|
||||
<!-- TODO backup API Key -->
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
@@ -48,7 +52,7 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
<data android:mimeType="*/*" />
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*\\.kdb" />
|
||||
<data android:pathPattern=".*\\..*\\.kdb" />
|
||||
@@ -71,29 +75,28 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:mimeType="application/octet-stream"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Folder picker -->
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
<activity
|
||||
android:name=".activities.stylish.FilePickerStylishActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="file" />
|
||||
<data android:scheme="content" />
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
<data android:mimeType="application/x-kdb" />
|
||||
<data android:mimeType="application/x-kdbx" />
|
||||
<data android:mimeType="application/x-keepass" />
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*" />
|
||||
<data android:pathPattern=".*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Main Activity -->
|
||||
@@ -129,9 +132,11 @@
|
||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
|
||||
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
|
||||
android:configChanges="keyboardHidden" />
|
||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
|
||||
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
||||
android:label="@string/keyboard_name">
|
||||
android:label="@string/keyboard_name"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
|
||||
android:label="@string/keyboard_setting_label">
|
||||
@@ -140,11 +145,18 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<service
|
||||
android:name="com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".notifications.AttachmentFileNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
|
||||
android:enabled="true"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
v7<EFBFBD><07>gx<67><78><EFBFBD>"<04>Dm<44>]tIWRP<52>g<18>y<15>/˰1<CBB0><31><13>X<0B><>fW[<5B>F%<25><1E>\<5C>up4
|
||||
<EFBFBD><EFBFBD>-t;<3B>z<EFBFBD>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<KeyFile>
|
||||
<Meta>
|
||||
<Version>1.00</Version>
|
||||
</Meta>
|
||||
<Key>
|
||||
<Data>zaTWphVNtRbspnwkqjy8FGTy5IqCUx9+FNb5H+VdB24=</Data>
|
||||
</Key>
|
||||
</KeyFile>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,27 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.widget.TextView
|
||||
|
||||
@@ -1,68 +1,90 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.design.widget.CollapsingToolbarLayout
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||
import com.kunzisoft.keepass.database.element.PwNodeId
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachment
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields
|
||||
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
|
||||
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.utils.createDocument
|
||||
import com.kunzisoft.keepass.utils.onCreateDocumentResult
|
||||
import com.kunzisoft.keepass.view.EntryContentsView
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class EntryActivity : LockingHideActivity() {
|
||||
class EntryActivity : LockingActivity() {
|
||||
|
||||
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
|
||||
private var titleIconView: ImageView? = null
|
||||
private var historyView: View? = null
|
||||
private var entryContentsView: EntryContentsView? = null
|
||||
private var entryProgress: ProgressBar? = null
|
||||
private var toolbar: Toolbar? = null
|
||||
|
||||
private var mEntry: EntryVersioned? = null
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
private var mEntry: Entry? = null
|
||||
|
||||
private var mIsHistory: Boolean = false
|
||||
private var mEntryLastVersion: Entry? = null
|
||||
private var mEntryHistoryPosition: Int = -1
|
||||
|
||||
private var mShowPassword: Boolean = false
|
||||
|
||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
|
||||
|
||||
private var clipboardHelper: ClipboardHelper? = null
|
||||
private var firstLaunchOfActivity: Boolean = false
|
||||
|
||||
@@ -78,19 +100,66 @@ class EntryActivity : LockingHideActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
val currentDatabase = Database.getInstance()
|
||||
readOnly = currentDatabase.isReadOnly || readOnly
|
||||
mDatabase = Database.getInstance()
|
||||
mReadOnly = mDatabase!!.isReadOnly || mReadOnly
|
||||
|
||||
mShowPassword = !PreferencesUtil.isPasswordMask(this)
|
||||
|
||||
// Retrieve the textColor to tint the icon
|
||||
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||
iconColor = taIconColor.getColor(0, Color.BLACK)
|
||||
taIconColor.recycle()
|
||||
|
||||
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
|
||||
invalidateOptionsMenu()
|
||||
|
||||
// Get views
|
||||
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
|
||||
titleIconView = findViewById(R.id.entry_icon)
|
||||
historyView = findViewById(R.id.history_container)
|
||||
entryContentsView = findViewById(R.id.entry_contents)
|
||||
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
||||
entryProgress = findViewById(R.id.entry_progress)
|
||||
|
||||
// Init the clipboard helper
|
||||
clipboardHelper = ClipboardHelper(this)
|
||||
firstLaunchOfActivity = true
|
||||
|
||||
// Init attachment service binder manager
|
||||
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
|
||||
|
||||
mProgressDialogThread?.onActionFinish = { actionTask, result ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
|
||||
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
|
||||
// Close the current activity after an history action
|
||||
if (result.isSuccess)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
// TODO Visual error for entry history
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Get Entry from UUID
|
||||
try {
|
||||
val keyEntry: PwNodeId<*> = intent.getParcelableExtra(KEY_ENTRY)
|
||||
mEntry = currentDatabase.getEntryById(keyEntry)
|
||||
val keyEntry: NodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
|
||||
mEntry = mDatabase?.getEntryById(keyEntry)
|
||||
mEntryLastVersion = mEntry
|
||||
} catch (e: ClassCastException) {
|
||||
Log.e(TAG, "Unable to retrieve the entry key")
|
||||
}
|
||||
|
||||
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
|
||||
mEntryHistoryPosition = historyPosition
|
||||
if (historyPosition >= 0) {
|
||||
mIsHistory = true
|
||||
mEntry = mEntry?.getHistory()?.get(historyPosition)
|
||||
}
|
||||
|
||||
if (mEntry == null) {
|
||||
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
@@ -100,28 +169,6 @@ class EntryActivity : LockingHideActivity() {
|
||||
// Update last access time.
|
||||
mEntry?.touch(modified = false, touchParents = false)
|
||||
|
||||
// Retrieve the textColor to tint the icon
|
||||
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
|
||||
iconColor = taIconColor.getColor(0, Color.WHITE)
|
||||
taIconColor.recycle()
|
||||
|
||||
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
|
||||
invalidateOptionsMenu()
|
||||
|
||||
// Get views
|
||||
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
|
||||
titleIconView = findViewById(R.id.entry_icon)
|
||||
entryContentsView = findViewById(R.id.entry_contents)
|
||||
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
||||
|
||||
// Init the clipboard helper
|
||||
clipboardHelper = ClipboardHelper(this)
|
||||
firstLaunchOfActivity = true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
mEntry?.let { entry ->
|
||||
// Fill data in resume to update from EntryEditActivity
|
||||
fillEntryDataInContentsView(entry)
|
||||
@@ -141,10 +188,25 @@ class EntryActivity : LockingHideActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
mAttachmentFileBinderManager?.apply {
|
||||
registerProgressTask()
|
||||
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
|
||||
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
|
||||
entryContentsView?.updateAttachmentDownloadProgress(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
firstLaunchOfActivity = false
|
||||
}
|
||||
|
||||
private fun fillEntryDataInContentsView(entry: EntryVersioned) {
|
||||
override fun onPause() {
|
||||
mAttachmentFileBinderManager?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private fun fillEntryDataInContentsView(entry: Entry) {
|
||||
|
||||
val database = Database.getInstance()
|
||||
database.startManageEntry(entry)
|
||||
@@ -152,95 +214,165 @@ class EntryActivity : LockingHideActivity() {
|
||||
titleIconView?.assignDatabaseIcon(database.drawFactory, entry.icon, iconColor)
|
||||
|
||||
// Assign title text
|
||||
val entryTitle = entry.getVisualTitle()
|
||||
val entryTitle = entry.title
|
||||
collapsingToolbarLayout?.title = entryTitle
|
||||
toolbar?.title = entryTitle
|
||||
|
||||
// Assign basic fields
|
||||
entryContentsView?.assignUserName(entry.username)
|
||||
entryContentsView?.assignUserNameCopyListener(View.OnClickListener {
|
||||
database.startManageEntry(entry)
|
||||
clipboardHelper?.timeoutCopyToClipboard(entry.username,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_user_name)))
|
||||
database.stopManageEntry(entry)
|
||||
})
|
||||
|
||||
val allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
|
||||
entryContentsView?.assignPassword(entry.password, allowCopyPassword)
|
||||
if (allowCopyPassword) {
|
||||
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
|
||||
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)
|
||||
val allowCopyPasswordAndProtectedFields =
|
||||
PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
|
||||
|
||||
val showWarningClipboardDialogOnClickListener = View.OnClickListener {
|
||||
AlertDialog.Builder(this@EntryActivity)
|
||||
.setMessage(getString(R.string.allow_copy_password_warning) +
|
||||
"\n\n" +
|
||||
getString(R.string.clipboard_warning))
|
||||
.create().apply {
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
entryContentsView?.assignPassword(entry.password, allowCopyPasswordAndProtectedFields)
|
||||
if (allowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
||||
database.startManageEntry(entry)
|
||||
clipboardHelper?.timeoutCopyToClipboard(entry.password,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_password)))
|
||||
database.stopManageEntry(entry)
|
||||
})
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
|
||||
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
||||
val message = getString(R.string.allow_copy_password_warning) +
|
||||
"\n\n" +
|
||||
getString(R.string.clipboard_warning)
|
||||
val warningDialog = AlertDialog.Builder(this@EntryActivity)
|
||||
.setMessage(message).create()
|
||||
warningDialog.setButton(AlertDialog.BUTTON_POSITIVE, getText(android.R.string.ok)
|
||||
) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
warningDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel)
|
||||
) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
}
|
||||
warningDialog.show()
|
||||
})
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.assignPasswordCopyListener(showWarningClipboardDialogOnClickListener)
|
||||
} else {
|
||||
entryContentsView?.assignPasswordCopyListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
//Assign OTP field
|
||||
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
|
||||
View.OnClickListener {
|
||||
entry.getOtpElement()?.let { otpElement ->
|
||||
clipboardHelper?.timeoutCopyToClipboard(
|
||||
otpElement.token,
|
||||
getString(R.string.copy_field, getString(R.string.entry_otp))
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
entryContentsView?.assignURL(entry.url)
|
||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||
entryContentsView?.assignComment(entry.notes)
|
||||
|
||||
// Assign custom fields
|
||||
if (entry.allowExtraFields()) {
|
||||
if (entry.allowCustomFields()) {
|
||||
entryContentsView?.clearExtraFields()
|
||||
|
||||
entry.fields.doActionToAllCustomProtectedField { label, value ->
|
||||
val showAction = !value.isProtected || PreferencesUtil.allowCopyPasswordAndProtectedFields(this@EntryActivity)
|
||||
entryContentsView?.addExtraField(label, value, showAction, View.OnClickListener {
|
||||
clipboardHelper?.timeoutCopyToClipboard(
|
||||
value.toString(),
|
||||
getString(R.string.copy_field, label)
|
||||
)
|
||||
})
|
||||
for (element in entry.customFields.entries) {
|
||||
val label = element.key
|
||||
val value = element.value
|
||||
|
||||
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
||||
if (allowCopyProtectedField) {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, View.OnClickListener {
|
||||
clipboardHelper?.timeoutCopyToClipboard(
|
||||
value.toString(),
|
||||
getString(R.string.copy_field, label)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
|
||||
} else {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||
|
||||
// Manage attachments
|
||||
val attachments = entry.getAttachments()
|
||||
val showAttachmentsView = attachments.isNotEmpty()
|
||||
entryContentsView?.showAttachments(showAttachmentsView)
|
||||
if (showAttachmentsView) {
|
||||
entryContentsView?.assignAttachments(attachments)
|
||||
entryContentsView?.onAttachmentClick { attachmentItem, _ ->
|
||||
when (attachmentItem.downloadState) {
|
||||
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
|
||||
createDocument(this, attachmentItem.name)?.let { requestCode ->
|
||||
mAttachmentsToDownload[requestCode] = attachmentItem
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO Stop download
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entryContentsView?.refreshAttachments()
|
||||
|
||||
// Assign dates
|
||||
entry.creationTime.date?.let {
|
||||
entryContentsView?.assignCreationDate(it)
|
||||
}
|
||||
entry.lastModificationTime.date?.let {
|
||||
entryContentsView?.assignModificationDate(it)
|
||||
}
|
||||
entry.lastAccessTime.date?.let {
|
||||
entryContentsView?.assignLastAccessDate(it)
|
||||
}
|
||||
val expires = entry.expiryTime.date
|
||||
if (entry.isExpires && expires != null) {
|
||||
entryContentsView?.assignExpiresDate(expires)
|
||||
entryContentsView?.assignCreationDate(entry.creationTime)
|
||||
entryContentsView?.assignModificationDate(entry.lastModificationTime)
|
||||
entryContentsView?.assignLastAccessDate(entry.lastAccessTime)
|
||||
entryContentsView?.setExpires(entry.isCurrentlyExpires)
|
||||
if (entry.expires) {
|
||||
entryContentsView?.assignExpiresDate(entry.expiryTime)
|
||||
} else {
|
||||
entryContentsView?.assignExpiresDate(getString(R.string.never))
|
||||
}
|
||||
|
||||
// Manage history
|
||||
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
|
||||
if (mIsHistory) {
|
||||
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
|
||||
taColorAccent.recycle()
|
||||
}
|
||||
val entryHistory = entry.getHistory()
|
||||
// TODO isMainEntry = not an history
|
||||
val showHistoryView = entryHistory.isNotEmpty()
|
||||
entryContentsView?.showHistory(showHistoryView)
|
||||
if (showHistoryView) {
|
||||
entryContentsView?.assignHistory(entryHistory)
|
||||
entryContentsView?.onHistoryClick { historyItem, position ->
|
||||
launch(this, historyItem, mReadOnly, position)
|
||||
}
|
||||
}
|
||||
entryContentsView?.refreshHistory()
|
||||
|
||||
// Assign special data
|
||||
entryContentsView?.assignUUID(entry.nodeId.id)
|
||||
|
||||
database.stopManageEntry(entry)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
when (requestCode) {
|
||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
|
||||
// Not directly get the entry from intent data but from database
|
||||
@@ -248,6 +380,15 @@ class EntryActivity : LockingHideActivity() {
|
||||
fillEntryDataInContentsView(it)
|
||||
}
|
||||
}
|
||||
|
||||
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
|
||||
if (createdFileUri != null) {
|
||||
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
|
||||
mAttachmentFileBinderManager
|
||||
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
|
||||
@@ -266,9 +407,12 @@ class EntryActivity : LockingHideActivity() {
|
||||
val inflater = menuInflater
|
||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||
inflater.inflate(R.menu.entry, menu)
|
||||
inflater.inflate(R.menu.database_lock, menu)
|
||||
|
||||
if (readOnly) {
|
||||
inflater.inflate(R.menu.database, menu)
|
||||
if (mIsHistory && !mReadOnly) {
|
||||
inflater.inflate(R.menu.entry_history, menu)
|
||||
}
|
||||
if (mIsHistory || mReadOnly) {
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
menu.findItem(R.id.menu_edit)?.isVisible = false
|
||||
}
|
||||
|
||||
@@ -303,7 +447,7 @@ class EntryActivity : LockingHideActivity() {
|
||||
|
||||
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
||||
menu: Menu) {
|
||||
if (entryContentsView?.isUserNamePresent == true
|
||||
val entryCopyEducationPerformed = entryContentsView?.isUserNamePresent == true
|
||||
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
||||
findViewById(R.id.entry_user_name_action_image),
|
||||
{
|
||||
@@ -314,38 +458,41 @@ class EntryActivity : LockingHideActivity() {
|
||||
{
|
||||
// Launch autofill settings
|
||||
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
|
||||
}))
|
||||
else if (toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
||||
toolbar!!.findViewById(R.id.menu_edit),
|
||||
{
|
||||
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
||||
},
|
||||
{
|
||||
// Open Keepass doc to create field references
|
||||
startActivity(Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(getString(R.string.field_references_url))))
|
||||
}))
|
||||
;
|
||||
})
|
||||
|
||||
if (!entryCopyEducationPerformed) {
|
||||
// entryEditEducationPerformed
|
||||
toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
||||
toolbar!!.findViewById(R.id.menu_edit),
|
||||
{
|
||||
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
||||
},
|
||||
{
|
||||
// Open Keepass doc to create field references
|
||||
startActivity(Intent(Intent.ACTION_VIEW,
|
||||
UriUtil.parse(getString(R.string.field_references_url))))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
|
||||
|
||||
R.id.menu_contribute -> {
|
||||
MenuUtil.onContributionItemSelected(this)
|
||||
return true
|
||||
}
|
||||
R.id.menu_toggle_pass -> {
|
||||
mShowPassword = !mShowPassword
|
||||
changeShowPasswordIcon(item)
|
||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_edit -> {
|
||||
mEntry?.let {
|
||||
EntryEditActivity.launch(this@EntryActivity, it)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_goto_url -> {
|
||||
var url: String = mEntry?.url ?: ""
|
||||
|
||||
@@ -354,23 +501,34 @@ class EntryActivity : LockingHideActivity() {
|
||||
url = "http://$url"
|
||||
}
|
||||
|
||||
try {
|
||||
Util.gotoUrl(this, url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
UriUtil.gotoUrl(this, url)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_restore_entry_history -> {
|
||||
mEntryLastVersion?.let { mainEntry ->
|
||||
mProgressDialogThread?.startDatabaseRestoreEntryHistory(
|
||||
mainEntry,
|
||||
mEntryHistoryPosition,
|
||||
!mReadOnly && mAutoSaveEnable)
|
||||
}
|
||||
}
|
||||
R.id.menu_delete_entry_history -> {
|
||||
mEntryLastVersion?.let { mainEntry ->
|
||||
mProgressDialogThread?.startDatabaseDeleteEntryHistory(
|
||||
mainEntry,
|
||||
mEntryHistoryPosition,
|
||||
!mReadOnly && mAutoSaveEnable)
|
||||
}
|
||||
}
|
||||
R.id.menu_lock -> {
|
||||
lockAndExit()
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_save_database -> {
|
||||
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||
}
|
||||
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@@ -381,7 +539,7 @@ class EntryActivity : LockingHideActivity() {
|
||||
TODO Slowdown when add entry as result
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
|
||||
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
|
||||
onFinish(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
|
||||
*/
|
||||
super.finish()
|
||||
}
|
||||
@@ -389,13 +547,16 @@ class EntryActivity : LockingHideActivity() {
|
||||
companion object {
|
||||
private val TAG = EntryActivity::class.java.name
|
||||
|
||||
const val KEY_ENTRY = "entry"
|
||||
const val KEY_ENTRY = "KEY_ENTRY"
|
||||
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
|
||||
|
||||
fun launch(activity: Activity, pw: EntryVersioned, readOnly: Boolean) {
|
||||
fun launch(activity: Activity, entry: Entry, readOnly: Boolean, historyPosition: Int? = null) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, pw.nodeId)
|
||||
intent.putExtra(KEY_ENTRY, entry.nodeId)
|
||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||
if (historyPosition != null)
|
||||
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
|
||||
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
@@ -22,45 +22,54 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ScrollView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
|
||||
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
|
||||
import com.kunzisoft.keepass.database.action.node.ActionNodeValues
|
||||
import com.kunzisoft.keepass.database.action.node.AddEntryRunnable
|
||||
import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable
|
||||
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import java.util.*
|
||||
|
||||
class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener {
|
||||
class EntryEditActivity : LockingActivity(),
|
||||
IconPickerDialogFragment.IconPickerListener,
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||
SetOTPDialogFragment.CreateOtpListener {
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
// Refs of an entry and group in database, are not modifiable
|
||||
private var mEntry: EntryVersioned? = null
|
||||
private var mParent: GroupVersioned? = null
|
||||
private var mEntry: Entry? = null
|
||||
private var mParent: Group? = null
|
||||
// New or copy of mEntry in the database to be modifiable
|
||||
private var mNewEntry: EntryVersioned? = null
|
||||
private var mNewEntry: Entry? = null
|
||||
private var mIsNew: Boolean = false
|
||||
|
||||
// Views
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var scrollView: ScrollView? = null
|
||||
|
||||
private var entryEditContentsView: EntryEditContentsView? = null
|
||||
|
||||
private var saveView: View? = null
|
||||
|
||||
// Education
|
||||
@@ -76,6 +85,8 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
|
||||
|
||||
scrollView = findViewById(R.id.entry_edit_scroll)
|
||||
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||
|
||||
@@ -84,11 +95,14 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
// Focus view to reinitialize timeout
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
|
||||
|
||||
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
mDatabase = Database.getInstance()
|
||||
|
||||
// Entry is retrieve, it's an entry to update
|
||||
intent.getParcelableExtra<PwNodeId<*>>(KEY_ENTRY)?.let {
|
||||
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
|
||||
mIsNew = false
|
||||
// Create an Entry copy to modify from the database entry
|
||||
mEntry = mDatabase?.getEntryById(it)
|
||||
@@ -103,25 +117,27 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the icon after an orientation change
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY) as EntryVersioned
|
||||
} else {
|
||||
// Create the new entry from the current one
|
||||
if (savedInstanceState == null
|
||||
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||
mEntry?.let { entry ->
|
||||
// Create a copy to modify
|
||||
mNewEntry = EntryVersioned(entry).also { newEntry ->
|
||||
|
||||
mNewEntry = Entry(entry).also { newEntry ->
|
||||
// WARNING Remove the parent to keep memory with parcelable
|
||||
newEntry.parent = null
|
||||
newEntry.removeParent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent is retrieve, it's a new entry to create
|
||||
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let {
|
||||
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
|
||||
mIsNew = true
|
||||
mNewEntry = mDatabase?.createEntry()
|
||||
// Create an empty new entry
|
||||
if (savedInstanceState == null
|
||||
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||
mNewEntry = mDatabase?.createEntry()
|
||||
}
|
||||
mParent = mDatabase?.getGroupById(it)
|
||||
// Add the default icon
|
||||
mDatabase?.drawFactory?.let { iconFactory ->
|
||||
@@ -129,6 +145,12 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the new entry after an orientation change
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY)
|
||||
}
|
||||
|
||||
// Close the activity if entry or parent can't be retrieve
|
||||
if (mNewEntry == null || mParent == null) {
|
||||
finish()
|
||||
@@ -150,40 +172,35 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
saveView = findViewById(R.id.entry_edit_save)
|
||||
saveView?.setOnClickListener { saveEntry() }
|
||||
|
||||
entryEditContentsView?.allowCustomField(mNewEntry?.allowExtraFields() == true) { addNewCustomField() }
|
||||
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) {
|
||||
addNewCustomField()
|
||||
}
|
||||
|
||||
// Verify the education views
|
||||
entryEditActivityEducation = EntryEditActivityEducation(this)
|
||||
entryEditActivityEducation?.let {
|
||||
Handler().post { performedNextEducation(it) }
|
||||
|
||||
// Create progress dialog
|
||||
mProgressDialogThread?.onActionFinish = { actionTask, result ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_CREATE_ENTRY_TASK,
|
||||
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
||||
if (result.isSuccess)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
// Show error
|
||||
if (!result.isSuccess) {
|
||||
result.message?.let { resultMessage ->
|
||||
Snackbar.make(coordinatorLayout!!,
|
||||
resultMessage,
|
||||
Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||
val passwordView = entryEditContentsView?.generatePasswordView
|
||||
val addNewFieldView = entryEditContentsView?.addNewFieldView
|
||||
|
||||
if (passwordView != null
|
||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||
passwordView,
|
||||
{
|
||||
openPasswordGenerator()
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryEditActivityEducation)
|
||||
}
|
||||
))
|
||||
else if (mNewEntry != null && mNewEntry!!.allowExtraFields() && !mNewEntry!!.containsCustomFields()
|
||||
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||
addNewFieldView,
|
||||
{
|
||||
addNewCustomField()
|
||||
}))
|
||||
;
|
||||
}
|
||||
|
||||
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
|
||||
private fun populateViewsWithEntry(newEntry: Entry) {
|
||||
// Don't start the field reference manager, we want to see the raw ref
|
||||
mDatabase?.stopManageEntry(newEntry)
|
||||
|
||||
@@ -193,30 +210,33 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
// Set info in view
|
||||
entryEditContentsView?.apply {
|
||||
title = newEntry.title
|
||||
username = newEntry.username
|
||||
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
|
||||
url = newEntry.url
|
||||
password = newEntry.password
|
||||
notes = newEntry.notes
|
||||
newEntry.fields.doActionToAllCustomProtectedField { key, value ->
|
||||
addNewCustomField(key, value)
|
||||
for (entry in newEntry.customFields.entries) {
|
||||
post {
|
||||
putCustomField(entry.key, entry.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateEntryWithViews(newEntry: EntryVersioned) {
|
||||
private fun populateEntryWithViews(newEntry: Entry) {
|
||||
|
||||
mDatabase?.startManageEntry(newEntry)
|
||||
|
||||
newEntry.apply {
|
||||
// Build info from view
|
||||
entryEditContentsView?.let { entryView ->
|
||||
removeAllFields()
|
||||
title = entryView.title
|
||||
username = entryView.username
|
||||
url = entryView.url
|
||||
password = entryView.password
|
||||
notes = entryView.notes
|
||||
entryView.customFields.forEach { customField ->
|
||||
addExtraField(customField.name, customField.protectedValue)
|
||||
putExtraField(customField.name, customField.protectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +244,7 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
mDatabase?.stopManageEntry(newEntry)
|
||||
}
|
||||
|
||||
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) {
|
||||
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
|
||||
mNewEntry?.icon = icon
|
||||
mDatabase?.drawFactory?.let { iconDrawFactory ->
|
||||
entryEditContentsView?.setIcon(iconDrawFactory, icon)
|
||||
@@ -242,9 +262,7 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
* Add a new customized field view and scroll to bottom
|
||||
*/
|
||||
private fun addNewCustomField() {
|
||||
entryEditContentsView?.addNewCustomField()
|
||||
// Scroll bottom
|
||||
scrollView?.post { scrollView?.fullScroll(ScrollView.FOCUS_DOWN) }
|
||||
entryEditContentsView?.addEmptyCustomField()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,47 +273,32 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
// Launch a validation and show the error if present
|
||||
if (entryEditContentsView?.isValid() == true) {
|
||||
// Clone the entry
|
||||
mDatabase?.let { database ->
|
||||
mNewEntry?.let { newEntry ->
|
||||
mNewEntry?.let { newEntry ->
|
||||
|
||||
// WARNING Add the parent previously deleted
|
||||
newEntry.parent = mEntry?.parent
|
||||
// Build info
|
||||
newEntry.lastAccessTime = PwDate()
|
||||
newEntry.lastModificationTime = PwDate()
|
||||
// WARNING Add the parent previously deleted
|
||||
newEntry.parent = mEntry?.parent
|
||||
// Build info
|
||||
newEntry.lastAccessTime = DateInstant()
|
||||
newEntry.lastModificationTime = DateInstant()
|
||||
|
||||
populateEntryWithViews(newEntry)
|
||||
populateEntryWithViews(newEntry)
|
||||
|
||||
// Open a progress dialog and save entry
|
||||
var actionRunnable: ActionRunnable? = null
|
||||
val afterActionNodeFinishRunnable = object : AfterActionNodeFinishRunnable() {
|
||||
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
|
||||
if (actionNodeValues.result.isSuccess)
|
||||
finish()
|
||||
}
|
||||
// Open a progress dialog and save entry
|
||||
if (mIsNew) {
|
||||
mParent?.let { parent ->
|
||||
mProgressDialogThread?.startDatabaseCreateEntry(
|
||||
newEntry,
|
||||
parent,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
if (mIsNew) {
|
||||
mParent?.let { parent ->
|
||||
actionRunnable = AddEntryRunnable(this@EntryEditActivity,
|
||||
database,
|
||||
newEntry,
|
||||
parent,
|
||||
afterActionNodeFinishRunnable,
|
||||
!readOnly)
|
||||
}
|
||||
|
||||
} else {
|
||||
mEntry?.let { oldEntry ->
|
||||
actionRunnable = UpdateEntryRunnable(this@EntryEditActivity,
|
||||
database,
|
||||
oldEntry,
|
||||
newEntry,
|
||||
afterActionNodeFinishRunnable,
|
||||
!readOnly)
|
||||
}
|
||||
}
|
||||
actionRunnable?.let { runnable ->
|
||||
ProgressDialogSaveDatabaseThread(this@EntryEditActivity) { runnable }.start()
|
||||
} else {
|
||||
mEntry?.let { oldEntry ->
|
||||
mProgressDialogThread?.startDatabaseUpdateEntry(
|
||||
oldEntry,
|
||||
newEntry,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,27 +309,80 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
val inflater = menuInflater
|
||||
inflater.inflate(R.menu.database_lock, menu)
|
||||
inflater.inflate(R.menu.database, menu)
|
||||
// Save database not needed here
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||
if (mDatabase?.allowOTP == true)
|
||||
inflater.inflate(R.menu.entry_otp, menu)
|
||||
|
||||
entryEditActivityEducation?.let {
|
||||
Handler().post { performedNextEducation(it) }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||
val passwordView = entryEditContentsView?.generatePasswordView
|
||||
val addNewFieldView = entryEditContentsView?.addNewFieldButton
|
||||
|
||||
val generatePasswordEducationPerformed = passwordView != null
|
||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||
passwordView,
|
||||
{
|
||||
openPasswordGenerator()
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryEditActivityEducation)
|
||||
}
|
||||
)
|
||||
if (!generatePasswordEducationPerformed) {
|
||||
// entryNewFieldEducationPerformed
|
||||
mNewEntry != null && mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
|
||||
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||
addNewFieldView,
|
||||
{
|
||||
addNewCustomField()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_lock -> {
|
||||
lockAndExit()
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
|
||||
|
||||
R.id.menu_save_database -> {
|
||||
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||
}
|
||||
R.id.menu_contribute -> {
|
||||
MenuUtil.onContributionItemSelected(this)
|
||||
return true
|
||||
}
|
||||
R.id.menu_add_otp -> {
|
||||
// Retrieve the current otpElement if exists
|
||||
// and open the dialog to set up the OTP
|
||||
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
|
||||
.show(supportFragmentManager, "addOTPDialog")
|
||||
return true
|
||||
}
|
||||
android.R.id.home -> finish()
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onOtpCreated(otpElement: OtpElement) {
|
||||
// Update the otp field with otpauth:// url
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement,
|
||||
mEntry?.title, mEntry?.username)
|
||||
entryEditContentsView?.putCustomField(otpField.name, otpField.protectedValue)
|
||||
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
|
||||
}
|
||||
|
||||
override fun iconPicked(bundle: Bundle) {
|
||||
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
||||
temporarilySaveAndShowSelectedIcon(icon)
|
||||
@@ -334,7 +390,10 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putParcelable(KEY_NEW_ENTRY, mNewEntry)
|
||||
mNewEntry?.let {
|
||||
populateEntryWithViews(it)
|
||||
outState.putParcelable(KEY_NEW_ENTRY, it)
|
||||
}
|
||||
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
@@ -397,7 +456,7 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
* @param activity from activity
|
||||
* @param pwEntry Entry to update
|
||||
*/
|
||||
fun launch(activity: Activity, pwEntry: EntryVersioned) {
|
||||
fun launch(activity: Activity, pwEntry: Entry) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
|
||||
@@ -411,7 +470,7 @@ class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPi
|
||||
* @param activity from activity
|
||||
* @param pwGroup Group who will contains new entry
|
||||
*/
|
||||
fun launch(activity: Activity, pwGroup: GroupVersioned) {
|
||||
fun launch(activity: Activity, pwGroup: Group) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, pwGroup.nodeId)
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.assist.AssistStructure
|
||||
import android.content.Intent
|
||||
@@ -29,81 +29,57 @@ import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.CreateFileDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.FileInformationDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||
import com.kunzisoft.keepass.fileselect.DeleteFileHistoryAsyncTask
|
||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
||||
import com.kunzisoft.keepass.fileselect.OpenFileHistoryAsyncTask
|
||||
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.magikeyboard.KeyboardHelper
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import net.cachapa.expandablelayout.ExpandableLayout
|
||||
import permissions.dispatcher.*
|
||||
import java.io.File
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import kotlinx.android.synthetic.main.activity_file_selection.*
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.URLDecoder
|
||||
import java.util.*
|
||||
|
||||
@RuntimePermissions
|
||||
class FileDatabaseSelectActivity : StylishActivity(),
|
||||
CreateFileDialogFragment.DefinePathDialogListener,
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
FileDatabaseHistoryAdapter.FileItemOpenListener,
|
||||
FileDatabaseHistoryAdapter.FileSelectClearListener,
|
||||
FileDatabaseHistoryAdapter.FileInformationShowListener {
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
|
||||
// Views
|
||||
private var fileListContainer: View? = null
|
||||
private var createButtonView: View? = null
|
||||
private var browseButtonView: View? = null
|
||||
private var openButtonView: View? = null
|
||||
private var fileSelectExpandableButtonView: View? = null
|
||||
private var fileSelectExpandableLayout: ExpandableLayout? = null
|
||||
private var openFileNameView: EditText? = null
|
||||
private var openDatabaseButtonView: View? = null
|
||||
|
||||
// Adapter to manage database history list
|
||||
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
|
||||
|
||||
private var mFileDatabaseHistory: FileDatabaseHistory? = null
|
||||
private var mFileDatabaseHistoryAction: FileDatabaseHistoryAction? = null
|
||||
|
||||
private var mDatabaseFileUri: Uri? = null
|
||||
|
||||
private var mKeyFileHelper: KeyFileHelper? = null
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private var mDefaultPath: String? = null
|
||||
private var mProgressDialogThread: ProgressDialogThread? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
mFileDatabaseHistory = FileDatabaseHistory.getInstance(WeakReference(applicationContext))
|
||||
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
|
||||
|
||||
setContentView(R.layout.activity_file_selection)
|
||||
fileListContainer = findViewById(R.id.container_file_list)
|
||||
@@ -112,132 +88,111 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
toolbar.title = ""
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
openFileNameView = findViewById(R.id.file_filename)
|
||||
|
||||
// Set the initial value of the filename
|
||||
mDefaultPath = (Environment.getExternalStorageDirectory().absolutePath
|
||||
+ getString(R.string.database_file_path_default)
|
||||
+ getString(R.string.database_file_name_default)
|
||||
+ getString(R.string.database_file_extension_default))
|
||||
openFileNameView?.setHint(R.string.open_link_database)
|
||||
|
||||
// Button to expand file selection
|
||||
fileSelectExpandableButtonView = findViewById(R.id.file_select_expandable_button)
|
||||
fileSelectExpandableLayout = findViewById(R.id.file_select_expandable)
|
||||
fileSelectExpandableButtonView?.setOnClickListener { _ ->
|
||||
if (fileSelectExpandableLayout?.isExpanded == true)
|
||||
fileSelectExpandableLayout?.collapse()
|
||||
else
|
||||
fileSelectExpandableLayout?.expand()
|
||||
// Create button
|
||||
createButtonView = findViewById(R.id.create_database_button)
|
||||
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
|
||||
// There is an activity which can handle this intent.
|
||||
createButtonView?.visibility = View.VISIBLE
|
||||
}
|
||||
else{
|
||||
// No Activity found that can handle this intent.
|
||||
createButtonView?.visibility = View.GONE
|
||||
}
|
||||
|
||||
createButtonView?.setOnClickListener { createNewFile() }
|
||||
|
||||
mOpenFileHelper = OpenFileHelper(this)
|
||||
openDatabaseButtonView = findViewById(R.id.open_database_button)
|
||||
openDatabaseButtonView?.setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||
|
||||
// History list
|
||||
val databaseFileListView = findViewById<RecyclerView>(R.id.file_list)
|
||||
databaseFileListView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
// Open button
|
||||
openButtonView = findViewById(R.id.open_database)
|
||||
openButtonView?.setOnClickListener { _ ->
|
||||
var fileName = openFileNameView?.text?.toString() ?: ""
|
||||
mDefaultPath?.let {
|
||||
if (fileName.isEmpty())
|
||||
fileName = it
|
||||
}
|
||||
launchPasswordActivityWithPath(fileName)
|
||||
}
|
||||
|
||||
// Create button
|
||||
createButtonView = findViewById(R.id.create_database)
|
||||
createButtonView?.setOnClickListener { openCreateFileDialogFragmentWithPermissionCheck() }
|
||||
|
||||
mKeyFileHelper = KeyFileHelper(this)
|
||||
browseButtonView = findViewById(R.id.browse_button)
|
||||
browseButtonView?.setOnClickListener(mKeyFileHelper!!.getOpenFileOnClickViewListener {
|
||||
Uri.parse("file://" + openFileNameView!!.text.toString())
|
||||
})
|
||||
|
||||
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
|
||||
fileDatabaseHistoryRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
|
||||
// Removes blinks
|
||||
(fileDatabaseHistoryRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
// Construct adapter with listeners
|
||||
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this@FileDatabaseSelectActivity,
|
||||
mFileDatabaseHistory?.databaseUriList ?: ArrayList())
|
||||
mAdapterDatabaseHistory?.setOnItemClickListener(this)
|
||||
mAdapterDatabaseHistory?.setFileSelectClearListener(this)
|
||||
mAdapterDatabaseHistory?.setFileInformationShowListener(this)
|
||||
databaseFileListView.adapter = mAdapterDatabaseHistory
|
||||
mAdapterDatabaseHistory = FileDatabaseHistoryAdapter(this)
|
||||
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
|
||||
UriUtil.parse(fileDatabaseHistoryEntityToOpen.databaseUri)?.let { databaseFileUri ->
|
||||
launchPasswordActivity(
|
||||
databaseFileUri,
|
||||
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
|
||||
}
|
||||
updateFileListVisibility()
|
||||
}
|
||||
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
||||
// Remove from app database
|
||||
mFileDatabaseHistoryAction?.deleteFileDatabaseHistory(fileDatabaseHistoryToDelete) { fileHistoryDeleted ->
|
||||
// Remove from adapter
|
||||
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
|
||||
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
updateFileListVisibility()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
mAdapterDatabaseHistory?.setOnSaveAliasListener { fileDatabaseHistoryWithNewAlias ->
|
||||
mFileDatabaseHistoryAction?.addOrUpdateFileDatabaseHistory(fileDatabaseHistoryWithNewAlias)
|
||||
}
|
||||
fileDatabaseHistoryRecyclerView.adapter = mAdapterDatabaseHistory
|
||||
|
||||
// Load default database if not an orientation change
|
||||
if (!(savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(EXTRA_STAY)
|
||||
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "")
|
||||
val databasePath = prefs.getString(PasswordActivity.KEY_DEFAULT_DATABASE_PATH, "")
|
||||
|
||||
if (fileName != null && fileName.isNotEmpty()) {
|
||||
val dbUri = UriUtil.parseUriFile(fileName)
|
||||
var scheme: String? = null
|
||||
if (dbUri != null)
|
||||
scheme = dbUri.scheme
|
||||
|
||||
if (scheme != null && scheme.isNotEmpty() && scheme.equals("file", ignoreCase = true)) {
|
||||
val path = dbUri!!.path
|
||||
val db = File(path!!)
|
||||
|
||||
if (db.exists()) {
|
||||
launchPasswordActivityWithPath(path)
|
||||
}
|
||||
} else {
|
||||
if (dbUri != null)
|
||||
launchPasswordActivityWithPath(dbUri.toString())
|
||||
}
|
||||
UriUtil.parse(databasePath)?.let { databaseFileUri ->
|
||||
launchPasswordActivityWithPath(databaseFileUri)
|
||||
} ?: run {
|
||||
Log.i(TAG, "Unable to launch Password Activity")
|
||||
}
|
||||
}
|
||||
|
||||
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
|
||||
// Retrieve the database URI provided by file manager after an orientation change
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(EXTRA_DATABASE_URI)) {
|
||||
mDatabaseFileUri = savedInstanceState.getParcelable(EXTRA_DATABASE_URI)
|
||||
}
|
||||
|
||||
// Attach the dialog thread to this activity
|
||||
mProgressDialogThread = ProgressDialogThread(this).apply {
|
||||
onActionFinish = { actionTask, _ ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_CREATE_TASK -> {
|
||||
// TODO Check
|
||||
// mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
// updateFileListVisibility()
|
||||
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
|
||||
// If no recent files
|
||||
if (createButtonView != null
|
||||
&& mFileDatabaseHistory != null
|
||||
&& !mFileDatabaseHistory!!.hasRecentFiles() && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
|
||||
createButtonView!!,
|
||||
{
|
||||
openCreateFileDialogFragmentWithPermissionCheck()
|
||||
},
|
||||
{
|
||||
// But if the user cancel, it can also select a database
|
||||
performedNextEducation(fileDatabaseSelectActivityEducation)
|
||||
}))
|
||||
else if (browseButtonView != null
|
||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
||||
browseButtonView!!,
|
||||
{tapTargetView ->
|
||||
tapTargetView?.let {
|
||||
mKeyFileHelper?.openFileOnClickViewListener?.onClick(it)
|
||||
}
|
||||
},
|
||||
{
|
||||
fileSelectExpandableButtonView?.let {
|
||||
fileDatabaseSelectActivityEducation
|
||||
.checkAndPerformedOpenLinkDatabaseEducation(it)
|
||||
}
|
||||
}
|
||||
))
|
||||
;
|
||||
/**
|
||||
* Create a new file by calling the content provider
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun createNewFile() {
|
||||
createDocument(this, getString(R.string.database_file_name_default) +
|
||||
getString(R.string.database_file_extension_default), "application/x-keepass")
|
||||
}
|
||||
|
||||
private fun fileNoFoundAction(e: FileNotFoundException) {
|
||||
val error = getString(R.string.file_not_found_content)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
error, Toast.LENGTH_LONG).show()
|
||||
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
Log.e(TAG, error, e)
|
||||
}
|
||||
|
||||
private fun launchPasswordActivity(fileName: String, keyFile: String) {
|
||||
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
try {
|
||||
PasswordActivity.launch(this@FileDatabaseSelectActivity,
|
||||
fileName, keyFile)
|
||||
databaseUri, keyFile)
|
||||
} catch (e: FileNotFoundException) {
|
||||
fileNoFoundAction(e)
|
||||
}
|
||||
@@ -245,7 +200,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
{
|
||||
try {
|
||||
PasswordActivity.launchForKeyboardResult(this@FileDatabaseSelectActivity,
|
||||
fileName, keyFile)
|
||||
databaseUri, keyFile)
|
||||
finish()
|
||||
} catch (e: FileNotFoundException) {
|
||||
fileNoFoundAction(e)
|
||||
@@ -255,7 +210,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
||||
fileName, keyFile,
|
||||
databaseUri, keyFile,
|
||||
assistStructure)
|
||||
} catch (e: FileNotFoundException) {
|
||||
fileNoFoundAction(e)
|
||||
@@ -265,8 +220,25 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
})
|
||||
}
|
||||
|
||||
private fun launchPasswordActivityWithPath(path: String) {
|
||||
launchPasswordActivity(path, "")
|
||||
private fun launchGroupActivity(readOnly: Boolean) {
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
GroupActivity.launch(this@FileDatabaseSelectActivity, readOnly)
|
||||
},
|
||||
{
|
||||
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity, readOnly)
|
||||
// Do not keep history
|
||||
finish()
|
||||
},
|
||||
{ assistStructure ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity, assistStructure, readOnly)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun launchPasswordActivityWithPath(databaseUri: Uri) {
|
||||
launchPasswordActivity(databaseUri, null)
|
||||
// Delete flickering for kitkat <=
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
overridePendingTransition(0, 0)
|
||||
@@ -292,29 +264,41 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val database = Database.getInstance()
|
||||
if (database.loaded) {
|
||||
launchGroupActivity(database.isReadOnly)
|
||||
}
|
||||
|
||||
super.onResume()
|
||||
|
||||
updateExternalStorageWarning()
|
||||
updateFileListVisibility()
|
||||
mAdapterDatabaseHistory!!.notifyDataSetChanged()
|
||||
|
||||
// Construct adapter with listeners
|
||||
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
|
||||
databaseFileHistoryList?.let {
|
||||
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
|
||||
updateFileListVisibility()
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
// Register progress task
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
// Unregister progress task
|
||||
mProgressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
// only to keep the current activity
|
||||
outState.putBoolean(EXTRA_STAY, true)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
// NOTE: delegate the permission handling to generated method
|
||||
onRequestPermissionsResult(requestCode, grantResults)
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
fun openCreateFileDialogFragment() {
|
||||
val createFileDialogFragment = CreateFileDialogFragment()
|
||||
createFileDialogFragment.show(supportFragmentManager, "createFileDialogFragment")
|
||||
// to retrieve the URI of a created database after an orientation change
|
||||
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
|
||||
}
|
||||
|
||||
private fun updateFileListVisibility() {
|
||||
@@ -324,133 +308,26 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
fileListContainer?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Create file for database
|
||||
* @return If not created, return false
|
||||
*/
|
||||
private fun createDatabaseFile(path: Uri): Boolean {
|
||||
|
||||
val pathString = URLDecoder.decode(path.path, "UTF-8")
|
||||
// Make sure file name exists
|
||||
if (pathString.isEmpty()) {
|
||||
Log.e(TAG, getString(R.string.error_filename_required))
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_filename_required,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
// Try to create the file
|
||||
val file = File(pathString)
|
||||
try {
|
||||
if (file.exists()) {
|
||||
Log.e(TAG, getString(R.string.error_database_exists) + " " + file)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_database_exists,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
val parent = file.parentFile
|
||||
|
||||
if (parent == null || parent.exists() && !parent.isDirectory) {
|
||||
Log.e(TAG, getString(R.string.error_invalid_path) + " " + file)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_invalid_path,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
if (!parent.exists()) {
|
||||
// Create parent directory
|
||||
if (!parent.mkdirs()) {
|
||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + parent)
|
||||
Toast.makeText(this@FileDatabaseSelectActivity,
|
||||
R.string.error_could_not_create_parent,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return file.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + e.localizedMessage)
|
||||
e.printStackTrace()
|
||||
Toast.makeText(
|
||||
this@FileDatabaseSelectActivity,
|
||||
getText(R.string.error_file_not_create).toString() + " "
|
||||
+ e.localizedMessage,
|
||||
Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDefinePathDialogPositiveClick(pathFile: Uri?): Boolean {
|
||||
mDatabaseFileUri = pathFile
|
||||
if (pathFile == null)
|
||||
return false
|
||||
return if (createDatabaseFile(pathFile)) {
|
||||
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
|
||||
true
|
||||
} else
|
||||
false
|
||||
}
|
||||
|
||||
override fun onDefinePathDialogNegativeClick(pathFile: Uri?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onAssignKeyDialogPositiveClick(
|
||||
masterPasswordChecked: Boolean, masterPassword: String?,
|
||||
keyFileChecked: Boolean, keyFile: Uri?) {
|
||||
|
||||
try {
|
||||
UriUtil.parseUriFile(mDatabaseFileUri)?.let { databaseUri ->
|
||||
mDatabaseFileUri?.let { databaseUri ->
|
||||
|
||||
// Create the new database
|
||||
ProgressDialogThread(this@FileDatabaseSelectActivity,
|
||||
{
|
||||
CreateDatabaseRunnable(this@FileDatabaseSelectActivity,
|
||||
databaseUri,
|
||||
Database.getInstance(),
|
||||
masterPasswordChecked,
|
||||
masterPassword,
|
||||
keyFileChecked,
|
||||
keyFile,
|
||||
true, // TODO get readonly
|
||||
LaunchGroupActivityFinish(databaseUri)
|
||||
)
|
||||
},
|
||||
R.string.progress_create)
|
||||
.start()
|
||||
mProgressDialogThread?.startDatabaseCreate(
|
||||
databaseUri,
|
||||
masterPasswordChecked,
|
||||
masterPassword,
|
||||
keyFileChecked,
|
||||
keyFile
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val error = "Unable to create database with this password and key file"
|
||||
Toast.makeText(this, error, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, error + " " + e.message)
|
||||
// TODO remove
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class LaunchGroupActivityFinish internal constructor(private val fileURI: Uri) : ActionRunnable() {
|
||||
|
||||
override fun run() {
|
||||
finishRun(true, null)
|
||||
}
|
||||
|
||||
override fun onFinishRun(result: Result) {
|
||||
runOnUiThread {
|
||||
if (result.isSuccess) {
|
||||
// Add database to recent files
|
||||
mFileDatabaseHistory?.addDatabaseUri(fileURI)
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
updateFileListVisibility()
|
||||
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
||||
} else {
|
||||
Log.e(TAG, "Unable to open the database")
|
||||
}
|
||||
}
|
||||
val error = getString(R.string.error_create_database_file)
|
||||
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
Log.e(TAG, error, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,29 +337,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
}
|
||||
|
||||
override fun onFileItemOpenListener(itemPosition: Int) {
|
||||
OpenFileHistoryAsyncTask({ fileName, keyFile ->
|
||||
if (fileName != null && keyFile != null)
|
||||
launchPasswordActivity(fileName, keyFile)
|
||||
updateFileListVisibility()
|
||||
}, mFileDatabaseHistory).execute(itemPosition)
|
||||
}
|
||||
|
||||
override fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel) {
|
||||
FileInformationDialogFragment.newInstance(fileDatabaseModel).show(supportFragmentManager, "fileInformation")
|
||||
}
|
||||
|
||||
override fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean {
|
||||
DeleteFileHistoryAsyncTask({
|
||||
fileDatabaseModel.fileUri?.let {
|
||||
mFileDatabaseHistory?.deleteDatabaseUri(it)
|
||||
}
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
updateFileListVisibility()
|
||||
}, mFileDatabaseHistory, mAdapterDatabaseHistory).execute(fileDatabaseModel)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
@@ -490,44 +344,64 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
if (PreferencesUtil.autoOpenSelectedFile(this@FileDatabaseSelectActivity)) {
|
||||
launchPasswordActivityWithPath(uri.toString())
|
||||
} else {
|
||||
fileSelectExpandableLayout?.expand(false)
|
||||
openFileNameView?.setText(uri.toString())
|
||||
}
|
||||
launchPasswordActivityWithPath(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showRationaleForExternalStorage(request: PermissionRequest) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_write_database)
|
||||
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() }
|
||||
.show()
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showDeniedForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showNeverAskForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show()
|
||||
// Retrieve the created URI from the file manager
|
||||
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
|
||||
mDatabaseFileUri = databaseFileCreatedUri
|
||||
if (mDatabaseFileUri != null) {
|
||||
AssignMasterKeyDialogFragment.getInstance(true)
|
||||
.show(supportFragmentManager, "passwordDialog")
|
||||
}
|
||||
// else {
|
||||
// TODO Show error
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
MenuUtil.defaultMenuInflater(menuInflater, menu)
|
||||
|
||||
Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
|
||||
// If no recent files
|
||||
val createDatabaseEducationPerformed = createButtonView != null && createButtonView!!.visibility == View.VISIBLE
|
||||
&& mAdapterDatabaseHistory != null
|
||||
&& mAdapterDatabaseHistory!!.itemCount > 0
|
||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
|
||||
createButtonView!!,
|
||||
{
|
||||
createNewFile()
|
||||
},
|
||||
{
|
||||
// But if the user cancel, it can also select a database
|
||||
performedNextEducation(fileDatabaseSelectActivityEducation)
|
||||
})
|
||||
if (!createDatabaseEducationPerformed) {
|
||||
// selectDatabaseEducationPerformed
|
||||
openDatabaseButtonView != null
|
||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
||||
openDatabaseButtonView!!,
|
||||
{tapTargetView ->
|
||||
tapTargetView?.let {
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
|
||||
}
|
||||
},
|
||||
{}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
|
||||
}
|
||||
@@ -536,6 +410,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
private const val TAG = "FileDbSelectActivity"
|
||||
private const val EXTRA_STAY = "EXTRA_STAY"
|
||||
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
@@ -550,7 +425,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
*/
|
||||
|
||||
fun launchForKeyboardSelection(activity: Activity) {
|
||||
KeyboardHelper.startActivityForKeyboardSelection(activity, Intent(activity, FileDatabaseSelectActivity::class.java))
|
||||
EntrySelectionHelper.startActivityForEntrySelection(activity, Intent(activity, FileDatabaseSelectActivity::class.java))
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.content.Context
|
||||
@@ -5,8 +24,8 @@ import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
@@ -14,29 +33,39 @@ import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ActionMode
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.NodeAdapter
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||
import com.kunzisoft.keepass.database.element.NodeVersioned
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import java.util.*
|
||||
|
||||
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
|
||||
|
||||
private var nodeClickCallback: NodeAdapter.NodeClickCallback? = null
|
||||
private var nodeMenuListener: NodeAdapter.NodeMenuListener? = null
|
||||
private var nodeClickListener: NodeClickListener? = null
|
||||
private var onScrollListener: OnScrollListener? = null
|
||||
|
||||
private var listView: RecyclerView? = null
|
||||
var mainGroup: GroupVersioned? = null
|
||||
var mainGroup: Group? = null
|
||||
private set
|
||||
private var mAdapter: NodeAdapter? = null
|
||||
|
||||
var nodeActionSelectionMode = false
|
||||
private set
|
||||
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
|
||||
private set
|
||||
private val listActionNodes = LinkedList<Node>()
|
||||
private val listPasteNodes = LinkedList<Node>()
|
||||
|
||||
private var notFoundView: View? = null
|
||||
private var isASearchResult: Boolean = false
|
||||
|
||||
@@ -52,31 +81,22 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
val isEmpty: Boolean
|
||||
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
|
||||
|
||||
override fun onAttach(context: Context?) {
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
nodeClickCallback = context as NodeAdapter.NodeClickCallback?
|
||||
nodeClickListener = context as NodeClickListener
|
||||
} catch (e: ClassCastException) {
|
||||
// The activity doesn't implement the interface, throw exception
|
||||
throw ClassCastException(context?.toString()
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + NodeAdapter.NodeClickCallback::class.java.name)
|
||||
}
|
||||
|
||||
try {
|
||||
nodeMenuListener = context as NodeAdapter.NodeMenuListener?
|
||||
} catch (e: ClassCastException) {
|
||||
nodeMenuListener = null
|
||||
// Context menu can be omit
|
||||
Log.w(TAG, context?.toString()
|
||||
+ " must implement " + NodeAdapter.NodeMenuListener::class.java.name)
|
||||
}
|
||||
|
||||
try {
|
||||
onScrollListener = context as OnScrollListener?
|
||||
onScrollListener = context as OnScrollListener
|
||||
} catch (e: ClassCastException) {
|
||||
onScrollListener = null
|
||||
// Context menu can be omit
|
||||
Log.w(TAG, context?.toString()
|
||||
Log.w(TAG, context.toString()
|
||||
+ " must implement " + RecyclerView.OnScrollListener::class.java.name)
|
||||
}
|
||||
}
|
||||
@@ -84,33 +104,58 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
activity?.let { currentActivity ->
|
||||
setHasOptionsMenu(true)
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
||||
|
||||
arguments?.let { args ->
|
||||
// Contains all the group in element
|
||||
if (args.containsKey(GROUP_KEY)) {
|
||||
mainGroup = args.getParcelable(GROUP_KEY)
|
||||
}
|
||||
if (args.containsKey(IS_SEARCH)) {
|
||||
isASearchResult = args.getBoolean(IS_SEARCH)
|
||||
}
|
||||
arguments?.let { args ->
|
||||
// Contains all the group in element
|
||||
if (args.containsKey(GROUP_KEY)) {
|
||||
mainGroup = args.getParcelable(GROUP_KEY)
|
||||
}
|
||||
|
||||
contextThemed?.let { context ->
|
||||
mAdapter = NodeAdapter(context, currentActivity.menuInflater)
|
||||
mAdapter?.apply {
|
||||
setReadOnly(readOnly)
|
||||
setIsASearchResult(isASearchResult)
|
||||
setOnNodeClickListener(nodeClickCallback)
|
||||
setActivateContextMenu(true)
|
||||
setNodeMenuListener(nodeMenuListener)
|
||||
}
|
||||
if (args.containsKey(IS_SEARCH)) {
|
||||
isASearchResult = args.getBoolean(IS_SEARCH)
|
||||
}
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
contextThemed?.let { context ->
|
||||
mAdapter = NodeAdapter(context)
|
||||
mAdapter?.apply {
|
||||
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
|
||||
override fun onNodeClick(node: Node) {
|
||||
if (nodeActionSelectionMode) {
|
||||
if (listActionNodes.contains(node)) {
|
||||
// Remove selected item if already selected
|
||||
listActionNodes.remove(node)
|
||||
} else {
|
||||
// Add selected item if not already selected
|
||||
listActionNodes.add(node)
|
||||
}
|
||||
nodeClickListener?.onNodeSelected(listActionNodes)
|
||||
setActionNodes(listActionNodes)
|
||||
notifyNodeChanged(node)
|
||||
} else {
|
||||
nodeClickListener?.onNodeClick(node)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNodeLongClick(node: Node): Boolean {
|
||||
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
|
||||
// Select the first item after a long click
|
||||
if (!listActionNodes.contains(node))
|
||||
listActionNodes.add(node)
|
||||
|
||||
nodeClickListener?.onNodeSelected(listActionNodes)
|
||||
|
||||
setActionNodes(listActionNodes)
|
||||
notifyNodeChanged(node)
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -129,7 +174,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
|
||||
onScrollListener?.let { onScrollListener ->
|
||||
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
onScrollListener.onScrolled(dy)
|
||||
}
|
||||
@@ -147,10 +192,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
activity?.intent?.let {
|
||||
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(it)
|
||||
}
|
||||
// Force read only mode if selection mode
|
||||
mAdapter?.apply {
|
||||
setReadOnly(readOnly)
|
||||
}
|
||||
|
||||
// Refresh data
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
@@ -194,35 +235,30 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
|
||||
inflater?.inflate(R.menu.tree, menu)
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.tree, menu)
|
||||
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
|
||||
R.id.menu_sort -> {
|
||||
context?.let { context ->
|
||||
val sortDialogFragment: SortDialogFragment
|
||||
|
||||
/*
|
||||
// TODO Recycle bin bottom
|
||||
if (database.isRecycleBinAvailable() && database.isRecycleBinEnabled()) {
|
||||
sortDialogFragment =
|
||||
val sortDialogFragment: SortDialogFragment =
|
||||
if (Database.getInstance().isRecycleBinEnabled) {
|
||||
SortDialogFragment.getInstance(
|
||||
PrefsUtil.getListSort(this),
|
||||
PrefsUtil.getAscendingSort(this),
|
||||
PrefsUtil.getGroupsBeforeSort(this),
|
||||
PrefsUtil.getRecycleBinBottomSort(this));
|
||||
} else {
|
||||
*/
|
||||
sortDialogFragment = SortDialogFragment.getInstance(
|
||||
PreferencesUtil.getListSort(context),
|
||||
PreferencesUtil.getAscendingSort(context),
|
||||
PreferencesUtil.getGroupsBeforeSort(context))
|
||||
//}
|
||||
PreferencesUtil.getListSort(context),
|
||||
PreferencesUtil.getAscendingSort(context),
|
||||
PreferencesUtil.getGroupsBeforeSort(context),
|
||||
PreferencesUtil.getRecycleBinBottomSort(context))
|
||||
} else {
|
||||
SortDialogFragment.getInstance(
|
||||
PreferencesUtil.getListSort(context),
|
||||
PreferencesUtil.getAscendingSort(context),
|
||||
PreferencesUtil.getGroupsBeforeSort(context))
|
||||
}
|
||||
|
||||
sortDialogFragment.show(childFragmentManager, "sortDialog")
|
||||
}
|
||||
@@ -233,6 +269,103 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
}
|
||||
|
||||
fun actionNodesCallback(nodes: List<Node>,
|
||||
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
|
||||
|
||||
return object : ActionMode.Callback {
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
nodeActionSelectionMode = false
|
||||
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
menu?.clear()
|
||||
|
||||
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
|
||||
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
|
||||
} else {
|
||||
nodeActionSelectionMode = true
|
||||
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
|
||||
|
||||
val database = Database.getInstance()
|
||||
|
||||
// Open and Edit for a single item
|
||||
if (nodes.size == 1) {
|
||||
// Edition
|
||||
if (readOnly
|
||||
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
|
||||
menu?.removeItem(R.id.menu_edit)
|
||||
}
|
||||
} else {
|
||||
menu?.removeItem(R.id.menu_open)
|
||||
menu?.removeItem(R.id.menu_edit)
|
||||
}
|
||||
|
||||
// Copy and Move (not for groups)
|
||||
if (readOnly
|
||||
|| isASearchResult
|
||||
|| nodes.any { it.type == Type.GROUP }) {
|
||||
// TODO COPY For Group
|
||||
menu?.removeItem(R.id.menu_copy)
|
||||
menu?.removeItem(R.id.menu_move)
|
||||
}
|
||||
|
||||
// Deletion
|
||||
if (readOnly
|
||||
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
|
||||
menu?.removeItem(R.id.menu_delete)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the number of items selected in title
|
||||
mode?.title = nodes.size.toString()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
if (menuListener == null)
|
||||
return false
|
||||
return when (item?.itemId) {
|
||||
R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0])
|
||||
R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0])
|
||||
R.id.menu_copy -> {
|
||||
nodeActionPasteMode = PasteMode.PASTE_FROM_COPY
|
||||
mAdapter?.unselectActionNodes()
|
||||
val returnValue = menuListener.onCopyMenuClick(nodes)
|
||||
nodeActionSelectionMode = false
|
||||
returnValue
|
||||
}
|
||||
R.id.menu_move -> {
|
||||
nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE
|
||||
mAdapter?.unselectActionNodes()
|
||||
val returnValue = menuListener.onMoveMenuClick(nodes)
|
||||
nodeActionSelectionMode = false
|
||||
returnValue
|
||||
}
|
||||
R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes)
|
||||
R.id.menu_paste -> {
|
||||
val returnValue = menuListener.onPasteMenuClick(nodeActionPasteMode, nodes)
|
||||
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||
nodeActionSelectionMode = false
|
||||
returnValue
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
listActionNodes.clear()
|
||||
listPasteNodes.clear()
|
||||
mAdapter?.unselectActionNodes()
|
||||
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||
nodeActionSelectionMode = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
@@ -240,7 +373,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|
||||
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||
data?.getParcelableExtra<NodeVersioned>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode ->
|
||||
data?.getParcelableExtra<Node>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode ->
|
||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
||||
mAdapter?.addNode(newNode)
|
||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||
@@ -255,18 +388,66 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
}
|
||||
|
||||
fun addNode(newNode: NodeVersioned) {
|
||||
fun contains(node: Node): Boolean {
|
||||
return mAdapter?.contains(node) ?: false
|
||||
}
|
||||
|
||||
fun addNode(newNode: Node) {
|
||||
mAdapter?.addNode(newNode)
|
||||
}
|
||||
|
||||
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) {
|
||||
mAdapter?.updateNode(oldNode, newNode)
|
||||
fun addNodes(newNodes: List<Node>) {
|
||||
mAdapter?.addNodes(newNodes)
|
||||
}
|
||||
|
||||
fun removeNode(pwNode: NodeVersioned) {
|
||||
fun updateNode(oldNode: Node, newNode: Node? = null) {
|
||||
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
|
||||
}
|
||||
|
||||
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||
mAdapter?.updateNodes(oldNodes, newNodes)
|
||||
}
|
||||
|
||||
fun removeNode(pwNode: Node) {
|
||||
mAdapter?.removeNode(pwNode)
|
||||
}
|
||||
|
||||
fun removeNodes(nodes: List<Node>) {
|
||||
mAdapter?.removeNodes(nodes)
|
||||
}
|
||||
|
||||
fun removeNodeAt(position: Int) {
|
||||
mAdapter?.removeNodeAt(position)
|
||||
}
|
||||
|
||||
fun removeNodesAt(positions: IntArray) {
|
||||
mAdapter?.removeNodesAt(positions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback listener to redefine to do an action when a node is click
|
||||
*/
|
||||
interface NodeClickListener {
|
||||
fun onNodeClick(node: Node)
|
||||
fun onNodeSelected(nodes: List<Node>): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu listener to redefine to do an action in menu
|
||||
*/
|
||||
interface NodesActionMenuListener {
|
||||
fun onOpenMenuClick(node: Node): Boolean
|
||||
fun onEditMenuClick(node: Node): Boolean
|
||||
fun onCopyMenuClick(nodes: List<Node>): Boolean
|
||||
fun onMoveMenuClick(nodes: List<Node>): Boolean
|
||||
fun onDeleteMenuClick(nodes: List<Node>): Boolean
|
||||
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
|
||||
}
|
||||
|
||||
enum class PasteMode {
|
||||
UNDEFINED, PASTE_FROM_COPY, PASTE_FROM_MOVE
|
||||
}
|
||||
|
||||
interface OnScrollListener {
|
||||
|
||||
/**
|
||||
@@ -285,7 +466,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
private const val GROUP_KEY = "GROUP_KEY"
|
||||
private const val IS_SEARCH = "IS_SEARCH"
|
||||
|
||||
fun newInstance(group: GroupVersioned?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
|
||||
fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
|
||||
val bundle = Bundle()
|
||||
if (group != null) {
|
||||
bundle.putParcelable(GROUP_KEY, group)
|
||||
|
||||
@@ -1,79 +1,85 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.assist.AssistStructure
|
||||
import android.app.backup.BackupManager
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
||||
import android.widget.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.*
|
||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.app.App
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper
|
||||
import com.kunzisoft.keepass.fingerprint.FingerPrintViewsManager
|
||||
import com.kunzisoft.keepass.magikeyboard.KeyboardHelper
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.KEY_FILE_KEY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.FingerPrintInfoView
|
||||
import permissions.dispatcher.*
|
||||
import java.io.File
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import kotlinx.android.synthetic.main.activity_password.*
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
@RuntimePermissions
|
||||
class PasswordActivity : StylishActivity(),
|
||||
UriIntentInitTaskCallback {
|
||||
class PasswordActivity : StylishActivity() {
|
||||
|
||||
// Views
|
||||
private var toolbar: Toolbar? = null
|
||||
|
||||
private var containerView: View? = null
|
||||
private var filenameView: TextView? = null
|
||||
private var passwordView: EditText? = null
|
||||
private var keyFileView: EditText? = null
|
||||
@@ -81,26 +87,30 @@ class PasswordActivity : StylishActivity(),
|
||||
private var checkboxPasswordView: CompoundButton? = null
|
||||
private var checkboxKeyFileView: CompoundButton? = null
|
||||
private var checkboxDefaultDatabaseView: CompoundButton? = null
|
||||
private var fingerPrintInfoView: FingerPrintInfoView? = null
|
||||
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
|
||||
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||
|
||||
private var mDatabaseFileUri: Uri? = null
|
||||
private var mDatabaseKeyFileUri: Uri? = null
|
||||
|
||||
private var prefs: SharedPreferences? = null
|
||||
|
||||
private var mRememberKeyFile: Boolean = false
|
||||
private var mKeyFileHelper: KeyFileHelper? = null
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private var mPermissionAsked = false
|
||||
private var readOnly: Boolean = false
|
||||
|
||||
private var fingerPrintViewsManager: FingerPrintViewsManager? = null
|
||||
private var mProgressDialogThread: ProgressDialogThread? = null
|
||||
|
||||
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
mRememberKeyFile = prefs!!.getBoolean(getString(R.string.keyfile_key),
|
||||
resources.getBoolean(R.bool.keyfile_default))
|
||||
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
|
||||
|
||||
setContentView(R.layout.activity_password)
|
||||
|
||||
@@ -110,6 +120,7 @@ class PasswordActivity : StylishActivity(),
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
containerView = findViewById(R.id.container)
|
||||
confirmButtonView = findViewById(R.id.pass_ok)
|
||||
filenameView = findViewById(R.id.filename)
|
||||
passwordView = findViewById(R.id.password)
|
||||
@@ -117,13 +128,14 @@ class PasswordActivity : StylishActivity(),
|
||||
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
||||
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
||||
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
|
||||
fingerPrintInfoView = findViewById(R.id.fingerprint_info)
|
||||
advancedUnlockInfoView = findViewById(R.id.biometric_info)
|
||||
|
||||
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||
|
||||
val browseView = findViewById<View>(R.id.browse_button)
|
||||
mKeyFileHelper = KeyFileHelper(this@PasswordActivity)
|
||||
browseView.setOnClickListener(mKeyFileHelper!!.openFileOnClickViewListener)
|
||||
val browseView = findViewById<View>(R.id.open_database_button)
|
||||
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
||||
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
|
||||
|
||||
passwordView?.setOnEditorActionListener(onEditorActionListener)
|
||||
passwordView?.addTextChangedListener(object : TextWatcher {
|
||||
@@ -148,13 +160,98 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
})
|
||||
|
||||
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
|
||||
if (!PreferencesUtil.emptyPasswordAllowed(this@PasswordActivity)) {
|
||||
confirmButtonView?.isEnabled = isChecked
|
||||
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
enableOrNotTheConfirmationButton()
|
||||
}
|
||||
|
||||
mProgressDialogThread = ProgressDialogThread(this).apply {
|
||||
onActionFinish = { actionTask, result ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_LOAD_TASK -> {
|
||||
// Recheck biometric if error
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
|
||||
// Stay with the same mode and init it
|
||||
advancedUnlockedManager?.initBiometricMode()
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the password in view in all cases
|
||||
removePassword()
|
||||
|
||||
if (result.isSuccess) {
|
||||
launchGroupActivity()
|
||||
} else {
|
||||
var resultError = ""
|
||||
val resultException = result.exception
|
||||
val resultMessage = result.message
|
||||
|
||||
if (resultException != null) {
|
||||
resultError = resultException.getLocalizedMessage(resources)
|
||||
|
||||
// Relaunch loading if we need to fix UUID
|
||||
if (resultException is DuplicateUuidDatabaseException) {
|
||||
showLoadDatabaseDuplicateUuidMessage {
|
||||
|
||||
var databaseUri: Uri? = null
|
||||
var masterPassword: String? = null
|
||||
var keyFileUri: Uri? = null
|
||||
var readOnly = true
|
||||
var cipherEntity: CipherDatabaseEntity? = null
|
||||
|
||||
result.data?.let { resultData ->
|
||||
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
|
||||
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
|
||||
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
|
||||
readOnly = resultData.getBoolean(READ_ONLY_KEY)
|
||||
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
|
||||
}
|
||||
|
||||
databaseUri?.let { databaseFileUri ->
|
||||
showProgressDialogAndLoadDatabase(
|
||||
databaseFileUri,
|
||||
masterPassword,
|
||||
keyFileUri,
|
||||
readOnly,
|
||||
cipherEntity,
|
||||
true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show error message
|
||||
if (resultMessage != null && resultMessage.isNotEmpty()) {
|
||||
resultError = "$resultError $resultMessage"
|
||||
}
|
||||
Log.e(TAG, resultError, resultException)
|
||||
Snackbar.make(activity_password_coordinator_layout,
|
||||
resultError,
|
||||
Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchGroupActivity() {
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
GroupActivity.launch(this@PasswordActivity, readOnly)
|
||||
},
|
||||
{
|
||||
GroupActivity.launchForKeyboardSelection(this@PasswordActivity, readOnly)
|
||||
// Do not keep history
|
||||
finish()
|
||||
},
|
||||
{ assistStructure ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private val onEditorActionListener = object : TextView.OnEditorActionListener {
|
||||
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||
if (actionId == IME_ACTION_DONE) {
|
||||
@@ -166,6 +263,9 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (Database.getInstance().loaded)
|
||||
launchGroupActivity()
|
||||
|
||||
// If the database isn't accessible make sure to clear the password field, if it
|
||||
// was saved in the instance state
|
||||
if (Database.getInstance().loaded) {
|
||||
@@ -175,44 +275,56 @@ class PasswordActivity : StylishActivity(),
|
||||
// For check shutdown
|
||||
super.onResume()
|
||||
|
||||
// Enable or not the open button
|
||||
if (!PreferencesUtil.emptyPasswordAllowed(this@PasswordActivity)) {
|
||||
checkboxPasswordView?.let {
|
||||
confirmButtonView?.isEnabled = it.isChecked
|
||||
}
|
||||
} else {
|
||||
confirmButtonView?.isEnabled = true
|
||||
}
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
|
||||
UriIntentInitTask(WeakReference(this), this, mRememberKeyFile)
|
||||
.execute(intent)
|
||||
initUriFromIntent()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onPostInitTask(databaseFileUri: Uri?, keyFileUri: Uri?, errorStringId: Int?) {
|
||||
mDatabaseFileUri = databaseFileUri
|
||||
private fun initUriFromIntent() {
|
||||
|
||||
if (errorStringId != null) {
|
||||
Toast.makeText(this@PasswordActivity, errorStringId, Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
return
|
||||
val databaseUri: Uri?
|
||||
val keyFileUri: Uri?
|
||||
|
||||
// If is a view intent
|
||||
val action = intent.action
|
||||
if (action != null
|
||||
&& action == VIEW_INTENT) {
|
||||
databaseUri = intent.data
|
||||
keyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
|
||||
} else {
|
||||
databaseUri = intent.getParcelableExtra(KEY_FILENAME)
|
||||
keyFileUri = intent.getParcelableExtra(KEY_KEYFILE)
|
||||
}
|
||||
|
||||
// Verify permission to read file
|
||||
if (databaseFileUri != null && !databaseFileUri.scheme!!.contains("content"))
|
||||
doNothingWithPermissionCheck()
|
||||
// Post init uri with KeyFile if needed
|
||||
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
|
||||
// Retrieve KeyFile in a thread
|
||||
databaseUri?.let { databaseUriNotNull ->
|
||||
FileDatabaseHistoryAction.getInstance(applicationContext)
|
||||
.getKeyFileUriByDatabaseUri(databaseUriNotNull) {
|
||||
onPostInitUri(databaseUri, it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onPostInitUri(databaseUri, keyFileUri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
|
||||
mDatabaseFileUri = databaseFileUri
|
||||
mDatabaseKeyFileUri = keyFileUri
|
||||
|
||||
// Define title
|
||||
val dbUriString = databaseFileUri?.toString() ?: ""
|
||||
if (dbUriString.isNotEmpty()) {
|
||||
if (PreferencesUtil.isFullFilePathEnable(this))
|
||||
filenameView?.text = dbUriString
|
||||
else
|
||||
filenameView?.text = File(databaseFileUri!!.path!!).name // TODO Encapsulate
|
||||
databaseFileUri?.let {
|
||||
FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
|
||||
filenameView?.text = title
|
||||
}
|
||||
}
|
||||
|
||||
// Define Key File text
|
||||
@@ -223,13 +335,17 @@ class PasswordActivity : StylishActivity(),
|
||||
|
||||
// Define listeners for default database checkbox and validate button
|
||||
checkboxDefaultDatabaseView?.setOnCheckedChangeListener { _, isChecked ->
|
||||
var newDefaultFileName = ""
|
||||
var newDefaultFileName: Uri? = null
|
||||
if (isChecked) {
|
||||
newDefaultFileName = databaseFileUri?.toString() ?: newDefaultFileName
|
||||
newDefaultFileName = databaseFileUri ?: newDefaultFileName
|
||||
}
|
||||
|
||||
prefs?.edit()?.apply() {
|
||||
putString(KEY_DEFAULT_FILENAME, newDefaultFileName)
|
||||
prefs?.edit()?.apply {
|
||||
newDefaultFileName?.let {
|
||||
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
|
||||
} ?: kotlin.run {
|
||||
remove(KEY_DEFAULT_DATABASE_PATH)
|
||||
}
|
||||
apply()
|
||||
}
|
||||
|
||||
@@ -239,10 +355,10 @@ class PasswordActivity : StylishActivity(),
|
||||
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
|
||||
|
||||
// Retrieve settings for default database
|
||||
val defaultFilename = prefs?.getString(KEY_DEFAULT_FILENAME, "")
|
||||
val defaultFilename = prefs?.getString(KEY_DEFAULT_DATABASE_PATH, "")
|
||||
if (databaseFileUri != null
|
||||
&& databaseFileUri.path != null && databaseFileUri.path!!.isNotEmpty()
|
||||
&& databaseFileUri == UriUtil.parseUriFile(defaultFilename)) {
|
||||
&& databaseFileUri == UriUtil.parse(defaultFilename)) {
|
||||
checkboxDefaultDatabaseView?.isChecked = true
|
||||
}
|
||||
|
||||
@@ -256,38 +372,61 @@ class PasswordActivity : StylishActivity(),
|
||||
if (launchImmediately) {
|
||||
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
|
||||
} else {
|
||||
// Init FingerPrint elements
|
||||
var fingerPrintInit = false
|
||||
// Init Biometric elements
|
||||
var biometricInitialize = false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (PreferencesUtil.isFingerprintEnable(this)) {
|
||||
if (fingerPrintViewsManager == null) {
|
||||
fingerPrintViewsManager = FingerPrintViewsManager(this,
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(this)) {
|
||||
|
||||
if (advancedUnlockedManager == null && databaseFileUri != null) {
|
||||
advancedUnlockedManager = AdvancedUnlockedManager(this,
|
||||
databaseFileUri,
|
||||
fingerPrintInfoView,
|
||||
advancedUnlockInfoView,
|
||||
checkboxPasswordView,
|
||||
enableButtonOnCheckedChangeListener,
|
||||
passwordView) { passwordRetrieve ->
|
||||
// Load the database if password is registered or retrieve
|
||||
passwordRetrieve?.let {
|
||||
// Retrieve from fingerprint
|
||||
verifyKeyFileCheckboxAndLoadDatabase(it)
|
||||
} ?: run {
|
||||
// Register with fingerprint
|
||||
verifyCheckboxesAndLoadDatabase()
|
||||
passwordView,
|
||||
{ passwordEncrypted, ivSpec ->
|
||||
// Load the database if password is registered with biometric
|
||||
if (passwordEncrypted != null && ivSpec != null) {
|
||||
verifyCheckboxesAndLoadDatabase(
|
||||
CipherDatabaseEntity(
|
||||
databaseFileUri.toString(),
|
||||
passwordEncrypted,
|
||||
ivSpec)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ passwordDecrypted ->
|
||||
// Load the database if password is retrieve from biometric
|
||||
passwordDecrypted?.let {
|
||||
// Retrieve from biometric
|
||||
verifyKeyFileCheckboxAndLoadDatabase(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
fingerPrintViewsManager?.initFingerprint()
|
||||
// checks if fingerprint is available, will also start listening for fingerprints when available
|
||||
fingerPrintViewsManager?.checkFingerprintAvailability()
|
||||
fingerPrintInit = true
|
||||
advancedUnlockedManager?.checkBiometricAvailability()
|
||||
biometricInitialize = true
|
||||
} else {
|
||||
fingerPrintViewsManager?.destroy()
|
||||
advancedUnlockedManager?.destroy()
|
||||
}
|
||||
}
|
||||
if (!fingerPrintInit) {
|
||||
if (!biometricInitialize) {
|
||||
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||
}
|
||||
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||
}
|
||||
|
||||
enableOrNotTheConfirmationButton()
|
||||
}
|
||||
|
||||
private fun enableOrNotTheConfirmationButton() {
|
||||
// Enable or not the open button if setting is checked
|
||||
if (!PreferencesUtil.emptyPasswordAllowed(this@PasswordActivity)) {
|
||||
checkboxPasswordView?.let {
|
||||
confirmButtonView?.isEnabled = (checkboxPasswordView?.isChecked == true
|
||||
|| checkboxKeyFileView?.isChecked == true)
|
||||
}
|
||||
} else {
|
||||
confirmButtonView?.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,30 +463,40 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerPrintViewsManager?.stopListening()
|
||||
}
|
||||
mProgressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerPrintViewsManager?.destroy()
|
||||
advancedUnlockedManager?.destroy()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun verifyCheckboxesAndLoadDatabase(password: String? = passwordView?.text?.toString(),
|
||||
keyFile: Uri? = UriUtil.parseUriFile(keyFileView?.text?.toString())) {
|
||||
val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
|
||||
val keyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
||||
loadDatabase(keyPassword, keyFileUri)
|
||||
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||
val password: String? = passwordView?.text?.toString()
|
||||
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
||||
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
|
||||
}
|
||||
|
||||
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String? = passwordView?.text?.toString(),
|
||||
keyFile: Uri? = UriUtil.parseUriFile(keyFileView?.text?.toString())) {
|
||||
val keyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
||||
loadDatabase(password, keyFileUri)
|
||||
private fun verifyCheckboxesAndLoadDatabase(password: String?,
|
||||
keyFile: Uri?,
|
||||
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||
val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
|
||||
verifyKeyFileCheckbox(keyFile)
|
||||
loadDatabase(mDatabaseFileUri, keyPassword, mDatabaseKeyFileUri, cipherDatabaseEntity)
|
||||
}
|
||||
|
||||
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
|
||||
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
||||
verifyKeyFileCheckbox(keyFile)
|
||||
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
|
||||
}
|
||||
|
||||
private fun verifyKeyFileCheckbox(keyFile: Uri?) {
|
||||
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
||||
}
|
||||
|
||||
private fun removePassword() {
|
||||
@@ -355,88 +504,47 @@ class PasswordActivity : StylishActivity(),
|
||||
checkboxPasswordView?.isChecked = false
|
||||
}
|
||||
|
||||
private fun loadDatabase(password: String?, keyFile: Uri?) {
|
||||
private fun loadDatabase(databaseFileUri: Uri?,
|
||||
password: String?,
|
||||
keyFileUri: Uri?,
|
||||
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||
|
||||
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
|
||||
removePassword()
|
||||
}
|
||||
|
||||
// Clear before we load
|
||||
val database = Database.getInstance()
|
||||
database.closeAndClear(applicationContext.filesDir)
|
||||
|
||||
mDatabaseFileUri?.let { databaseUri ->
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
// Show the progress dialog and load the database
|
||||
ProgressDialogThread(this,
|
||||
{ progressTaskUpdater ->
|
||||
LoadDatabaseRunnable(
|
||||
WeakReference(this@PasswordActivity),
|
||||
database,
|
||||
databaseUri,
|
||||
password,
|
||||
keyFile,
|
||||
progressTaskUpdater,
|
||||
AfterLoadingDatabase(database, password))
|
||||
},
|
||||
R.string.loading_database).start()
|
||||
showProgressDialogAndLoadDatabase(
|
||||
databaseUri,
|
||||
password,
|
||||
keyFileUri,
|
||||
readOnly,
|
||||
cipherDatabaseEntity,
|
||||
false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after verify and try to opening the database
|
||||
*/
|
||||
private inner class AfterLoadingDatabase internal constructor(var database: Database,
|
||||
val password: String?)
|
||||
: ActionRunnable() {
|
||||
|
||||
override fun onFinishRun(result: Result) {
|
||||
runOnUiThread {
|
||||
// Recheck fingerprint if error
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (PreferencesUtil.isFingerprintEnable(this@PasswordActivity)) {
|
||||
// Stay with the same mode
|
||||
fingerPrintViewsManager?.reInitWithFingerprintMode()
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isSuccess) {
|
||||
// Remove the password in view in all cases
|
||||
removePassword()
|
||||
|
||||
if (database.validatePasswordEncoding(password)) {
|
||||
launchGroupActivity()
|
||||
} else {
|
||||
PasswordEncodingDialogFragment().apply {
|
||||
positiveButtonClickListener = DialogInterface.OnClickListener { _, _ ->
|
||||
launchGroupActivity()
|
||||
}
|
||||
show(supportFragmentManager, "passwordEncodingTag")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (result.message != null && result.message!!.isNotEmpty()) {
|
||||
Toast.makeText(this@PasswordActivity, result.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
|
||||
password: String?,
|
||||
keyFile: Uri?,
|
||||
readOnly: Boolean,
|
||||
cipherDatabaseEntity: CipherDatabaseEntity?,
|
||||
fixDuplicateUUID: Boolean) {
|
||||
mProgressDialogThread?.startDatabaseLoad(
|
||||
databaseUri,
|
||||
password,
|
||||
keyFile,
|
||||
readOnly,
|
||||
cipherDatabaseEntity,
|
||||
fixDuplicateUUID
|
||||
)
|
||||
}
|
||||
|
||||
private fun launchGroupActivity() {
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
GroupActivity.launch(this@PasswordActivity, readOnly)
|
||||
},
|
||||
{
|
||||
GroupActivity.launchForKeyboardSelection(this@PasswordActivity, readOnly)
|
||||
// Do not keep history
|
||||
finish()
|
||||
},
|
||||
{ assistStructure ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly)
|
||||
}
|
||||
})
|
||||
private fun showLoadDatabaseDuplicateUuidMessage(loadDatabaseWithFix: (() -> Unit)? = null) {
|
||||
DuplicateUuidDialog().apply {
|
||||
positiveAction = loadDatabaseWithFix
|
||||
}.show(supportFragmentManager, "duplicateUUIDDialog")
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
@@ -448,46 +556,104 @@ class PasswordActivity : StylishActivity(),
|
||||
MenuUtil.defaultMenuInflater(inflater, menu)
|
||||
|
||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Fingerprint menu
|
||||
fingerPrintViewsManager?.inflateOptionsMenu(inflater, menu)
|
||||
// biometric menu
|
||||
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
|
||||
}
|
||||
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
// Show education views
|
||||
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
|
||||
launchEducation(menu) {
|
||||
launchCheckPermission()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Check permission
|
||||
private fun launchCheckPermission() {
|
||||
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
val permissions = arrayOf(writePermission)
|
||||
if (Build.VERSION.SDK_INT >= 23
|
||||
&& !readOnly
|
||||
&& !mPermissionAsked) {
|
||||
mPermissionAsked = true
|
||||
// Check self permission to show or not the dialog
|
||||
if (toolbar != null
|
||||
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
when (requestCode) {
|
||||
WRITE_EXTERNAL_STORAGE_REQUEST -> {
|
||||
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
|
||||
Toast.makeText(this, R.string.read_only_warning, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To fix multiple view education
|
||||
private var performedEductionInProgress = false
|
||||
private fun launchEducation(menu: Menu, onEducationFinished: ()-> Unit) {
|
||||
if (!performedEductionInProgress) {
|
||||
performedEductionInProgress = true
|
||||
// Show education views
|
||||
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
|
||||
menu: Menu) {
|
||||
if (toolbar != null
|
||||
&& passwordActivityEducation.checkAndPerformedFingerprintUnlockEducation(
|
||||
toolbar!!,
|
||||
menu: Menu,
|
||||
onEducationFinished: ()-> Unit) {
|
||||
val educationToolbar = toolbar
|
||||
val unlockEducationPerformed = educationToolbar != null
|
||||
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
|
||||
educationToolbar,
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
}))
|
||||
else if (toolbar != null
|
||||
&& toolbar!!.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
|
||||
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
|
||||
toolbar!!.findViewById(R.id.menu_open_file_read_mode_key),
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
})
|
||||
if (!unlockEducationPerformed) {
|
||||
val readOnlyEducationPerformed =
|
||||
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
|
||||
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
|
||||
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
|
||||
{
|
||||
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
})
|
||||
|
||||
if (!readOnlyEducationPerformed) {
|
||||
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
|
||||
val biometricEducationPerformed =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
|
||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
|
||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
|
||||
{
|
||||
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
}))
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& PreferencesUtil.isFingerprintEnable(applicationContext)
|
||||
&& FingerPrintHelper.isFingerprintSupported(getSystemService(FingerprintManager::class.java))
|
||||
&& fingerPrintInfoView != null && fingerPrintInfoView?.fingerPrintImageView != null
|
||||
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(fingerPrintInfoView?.fingerPrintImageView!!))
|
||||
;
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
})
|
||||
|
||||
if (!biometricEducationPerformed) {
|
||||
onEducationFinished.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
|
||||
@@ -508,8 +674,8 @@ class PasswordActivity : StylishActivity(),
|
||||
readOnly = !readOnly
|
||||
changeOpenFileReadIcon(item)
|
||||
}
|
||||
R.id.menu_fingerprint_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerPrintViewsManager?.deleteEntryKey()
|
||||
R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
advancedUnlockedManager?.deleteEntryKey()
|
||||
}
|
||||
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||
}
|
||||
@@ -517,12 +683,6 @@ class PasswordActivity : StylishActivity(),
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
// NOTE: delegate the permission handling to generated method
|
||||
onRequestPermissionsResult(requestCode, grantResults)
|
||||
}
|
||||
|
||||
override fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
@@ -535,7 +695,7 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
|
||||
var keyFileResult = false
|
||||
mKeyFileHelper?.let {
|
||||
mOpenFileHelper?.let {
|
||||
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
@@ -554,64 +714,32 @@ class PasswordActivity : StylishActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
fun doNothing() {
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showRationaleForExternalStorage(request: PermissionRequest) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_read_database)
|
||||
.setPositiveButton(R.string.allow) { _, _ -> request.proceed() }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> request.cancel() }
|
||||
.show()
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showDeniedForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
internal fun showNeverAskForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = PasswordActivity::class.java.name
|
||||
|
||||
const val KEY_DEFAULT_FILENAME = "defaultFileName"
|
||||
const val KEY_DEFAULT_DATABASE_PATH = "KEY_DEFAULT_DATABASE_PATH"
|
||||
|
||||
private const val KEY_FILENAME = "fileName"
|
||||
private const val KEY_KEYFILE = "keyFile"
|
||||
private const val VIEW_INTENT = "android.intent.action.VIEW"
|
||||
|
||||
private const val KEY_PASSWORD = "password"
|
||||
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
|
||||
|
||||
private fun buildAndLaunchIntent(activity: Activity, fileName: String, keyFile: String,
|
||||
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
|
||||
|
||||
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
|
||||
|
||||
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
|
||||
intentBuildLauncher: (Intent) -> Unit) {
|
||||
val intent = Intent(activity, PasswordActivity::class.java)
|
||||
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName)
|
||||
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile)
|
||||
intent.putExtra(KEY_FILENAME, databaseFile)
|
||||
if (keyFile != null)
|
||||
intent.putExtra(KEY_KEYFILE, keyFile)
|
||||
intentBuildLauncher.invoke(intent)
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
private fun verifyFileNameUriFromLaunch(fileName: String) {
|
||||
if (fileName.isEmpty()) {
|
||||
throw FileNotFoundException()
|
||||
}
|
||||
|
||||
val uri = UriUtil.parseUriFile(fileName)
|
||||
val scheme = uri?.scheme
|
||||
if (scheme != null && scheme.isNotEmpty() && scheme.equals("file", ignoreCase = true)) {
|
||||
val dbFile = File(uri.path!!)
|
||||
if (!dbFile.exists()) {
|
||||
throw FileNotFoundException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
* Standard Launch
|
||||
@@ -621,10 +749,9 @@ class PasswordActivity : StylishActivity(),
|
||||
@Throws(FileNotFoundException::class)
|
||||
fun launch(
|
||||
activity: Activity,
|
||||
fileName: String,
|
||||
keyFile: String) {
|
||||
verifyFileNameUriFromLaunch(fileName)
|
||||
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
|
||||
databaseFile: Uri,
|
||||
keyFile: Uri?) {
|
||||
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -638,12 +765,10 @@ class PasswordActivity : StylishActivity(),
|
||||
@Throws(FileNotFoundException::class)
|
||||
fun launchForKeyboardResult(
|
||||
activity: Activity,
|
||||
fileName: String,
|
||||
keyFile: String) {
|
||||
verifyFileNameUriFromLaunch(fileName)
|
||||
|
||||
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
|
||||
KeyboardHelper.startActivityForKeyboardSelection(activity, intent)
|
||||
databaseFile: Uri,
|
||||
keyFile: Uri?) {
|
||||
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||
EntrySelectionHelper.startActivityForEntrySelection(activity, intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,20 +782,18 @@ class PasswordActivity : StylishActivity(),
|
||||
@Throws(FileNotFoundException::class)
|
||||
fun launchForAutofillResult(
|
||||
activity: Activity,
|
||||
fileName: String,
|
||||
keyFile: String,
|
||||
databaseFile: Uri,
|
||||
keyFile: Uri?,
|
||||
assistStructure: AssistStructure?) {
|
||||
verifyFileNameUriFromLaunch(fileName)
|
||||
|
||||
if (assistStructure != null) {
|
||||
buildAndLaunchIntent(activity, fileName, keyFile) { intent ->
|
||||
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||
AutofillHelper.startActivityForAutofillResult(
|
||||
activity,
|
||||
intent,
|
||||
assistStructure)
|
||||
}
|
||||
} else {
|
||||
launch(activity, fileName, keyFile)
|
||||
launch(activity, databaseFile, keyFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
@@ -25,16 +25,16 @@ import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.KeyFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
@@ -43,15 +43,41 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
private var mKeyFile: Uri? = null
|
||||
|
||||
private var rootView: View? = null
|
||||
|
||||
private var passwordCheckBox: CompoundButton? = null
|
||||
private var passView: TextView? = null
|
||||
private var passConfView: TextView? = null
|
||||
|
||||
private var passwordTextInputLayout: TextInputLayout? = null
|
||||
private var passwordView: TextView? = null
|
||||
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
||||
private var passwordRepeatView: TextView? = null
|
||||
|
||||
private var keyFileTextInputLayout: TextInputLayout? = null
|
||||
private var keyFileCheckBox: CompoundButton? = null
|
||||
private var keyFileView: TextView? = null
|
||||
|
||||
private var mListener: AssignPasswordDialogListener? = null
|
||||
|
||||
private var mKeyFileHelper: KeyFileHelper? = null
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private val passwordTextWatcher = object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
passwordCheckBox?.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
private val keyFileTextWatcher = object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
keyFileCheckBox?.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
interface AssignPasswordDialogListener {
|
||||
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
|
||||
@@ -60,18 +86,25 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
keyFileChecked: Boolean, keyFile: Uri?)
|
||||
}
|
||||
|
||||
override fun onAttach(activity: Context?) {
|
||||
override fun onAttach(activity: Context) {
|
||||
super.onAttach(activity)
|
||||
try {
|
||||
mListener = activity as AssignPasswordDialogListener?
|
||||
mListener = activity as AssignPasswordDialogListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(activity?.toString()
|
||||
throw ClassCastException(activity.toString()
|
||||
+ " must implement " + AssignPasswordDialogListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
|
||||
var allowNoMasterKey = false
|
||||
arguments?.apply {
|
||||
if (containsKey(ALLOW_NO_MASTER_KEY_ARG))
|
||||
allowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
val inflater = activity.layoutInflater
|
||||
|
||||
@@ -80,36 +113,21 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
.setTitle(R.string.assign_master_key)
|
||||
// Add action buttons
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
|
||||
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
||||
passView = rootView?.findViewById(R.id.pass_password)
|
||||
passView?.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
passwordCheckBox?.isChecked = true
|
||||
}
|
||||
})
|
||||
passConfView = rootView?.findViewById(R.id.pass_conf_password)
|
||||
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
|
||||
passwordView = rootView?.findViewById(R.id.pass_password)
|
||||
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
||||
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
||||
|
||||
keyFileTextInputLayout = rootView?.findViewById(R.id.keyfile_input_layout)
|
||||
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
||||
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
|
||||
keyFileView?.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
keyFileCheckBox?.isChecked = true
|
||||
}
|
||||
})
|
||||
|
||||
mKeyFileHelper = KeyFileHelper(this)
|
||||
rootView?.findViewById<View>(R.id.browse_button)?.setOnClickListener { view ->
|
||||
mKeyFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
||||
mOpenFileHelper = OpenFileHelper(this)
|
||||
rootView?.findViewById<View>(R.id.open_database_button)?.setOnClickListener { view ->
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
@@ -124,7 +142,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
var error = verifyPassword() || verifyFile()
|
||||
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
||||
error = true
|
||||
showNoKeyConfirmationDialog()
|
||||
if (allowNoMasterKey)
|
||||
showNoKeyConfirmationDialog()
|
||||
else {
|
||||
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
|
||||
}
|
||||
}
|
||||
if (!error) {
|
||||
mListener?.onAssignKeyDialogPositiveClick(
|
||||
@@ -149,20 +171,35 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// To check checkboxes if a text is present
|
||||
passwordView?.addTextChangedListener(passwordTextWatcher)
|
||||
keyFileView?.addTextChangedListener(keyFileTextWatcher)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
passwordView?.removeTextChangedListener(passwordTextWatcher)
|
||||
keyFileView?.removeTextChangedListener(keyFileTextWatcher)
|
||||
}
|
||||
|
||||
private fun verifyPassword(): Boolean {
|
||||
var error = false
|
||||
if (passwordCheckBox != null
|
||||
&& passwordCheckBox!!.isChecked
|
||||
&& passView != null
|
||||
&& passConfView != null) {
|
||||
mMasterPassword = passView!!.text.toString()
|
||||
val confPassword = passConfView!!.text.toString()
|
||||
&& passwordView != null
|
||||
&& passwordRepeatView != null) {
|
||||
mMasterPassword = passwordView!!.text.toString()
|
||||
val confPassword = passwordRepeatView!!.text.toString()
|
||||
|
||||
// Verify that passwords match
|
||||
if (mMasterPassword != confPassword) {
|
||||
error = true
|
||||
// Passwords do not match
|
||||
Toast.makeText(context, R.string.error_pass_match, Toast.LENGTH_LONG).show()
|
||||
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
|
||||
}
|
||||
|
||||
if (mMasterPassword == null || mMasterPassword!!.isEmpty()) {
|
||||
@@ -170,6 +207,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
showEmptyPasswordConfirmationDialog()
|
||||
}
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
|
||||
@@ -177,13 +215,12 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
var error = false
|
||||
if (keyFileCheckBox != null
|
||||
&& keyFileCheckBox!!.isChecked) {
|
||||
val keyFile = UriUtil.parseUriFile(keyFileView?.text?.toString())
|
||||
mKeyFile = keyFile
|
||||
|
||||
// Verify that a keyfile is set
|
||||
if (keyFile == null || keyFile.toString().isEmpty()) {
|
||||
UriUtil.parse(keyFileView?.text?.toString())?.let { uri ->
|
||||
mKeyFile = uri
|
||||
} ?: run {
|
||||
error = true
|
||||
Toast.makeText(context, R.string.error_nokeyfile, Toast.LENGTH_LONG).show()
|
||||
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile)
|
||||
}
|
||||
}
|
||||
return error
|
||||
@@ -201,7 +238,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
this@AssignMasterKeyDialogFragment.dismiss()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
builder.create().show()
|
||||
}
|
||||
}
|
||||
@@ -216,7 +253,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
keyFileCheckBox!!.isChecked, mKeyFile)
|
||||
this@AssignMasterKeyDialogFragment.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
builder.create().show()
|
||||
}
|
||||
}
|
||||
@@ -224,13 +261,26 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
mKeyFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
UriUtil.parseUriFile(uri)?.let { pathUri ->
|
||||
uri?.let { pathUri ->
|
||||
keyFileCheckBox?.isChecked = true
|
||||
keyFileView?.text = pathUri.toString()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"
|
||||
|
||||
fun getInstance(allowNoMasterKey: Boolean): AssignMasterKeyDialogFragment {
|
||||
val fragment = AssignMasterKeyDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putBoolean(ALLOW_NO_MASTER_KEY_ARG, allowNoMasterKey)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class BrowserDialogFragment : DialogFragment() {
|
||||
|
||||
@@ -35,17 +36,20 @@ class BrowserDialogFragment : DialogFragment() {
|
||||
// Get the layout inflater
|
||||
val root = activity.layoutInflater.inflate(R.layout.fragment_browser_install, null)
|
||||
builder.setView(root)
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
|
||||
val market = root.findViewById<Button>(R.id.install_market)
|
||||
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 {
|
||||
Util.gotoUrl(context!!, R.string.filemanager_play_store)
|
||||
UriUtil.gotoUrl(context!!, R.string.file_manager_play_store)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
val web = root.findViewById<Button>(R.id.install_web)
|
||||
val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
|
||||
web.setOnClickListener {
|
||||
Util.gotoUrl(context!!, R.string.filemanager_f_droid)
|
||||
UriUtil.gotoUrl(context!!, R.string.file_manager_f_droid)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.stylish.FilePickerStylishActivity
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
||||
import com.nononsenseapps.filepicker.Utils
|
||||
|
||||
class CreateFileDialogFragment : DialogFragment(), AdapterView.OnItemSelectedListener {
|
||||
|
||||
private val FILE_CODE = 3853
|
||||
|
||||
private var folderPathView: EditText? = null
|
||||
private var fileNameView: EditText? = null
|
||||
private var positiveButton: Button? = null
|
||||
private var negativeButton: Button? = null
|
||||
|
||||
private var mDefinePathDialogListener: DefinePathDialogListener? = null
|
||||
|
||||
private var mDatabaseFileExtension: String? = null
|
||||
private var mUriPath: Uri? = null
|
||||
|
||||
interface DefinePathDialogListener {
|
||||
fun onDefinePathDialogPositiveClick(pathFile: Uri?): Boolean
|
||||
fun onDefinePathDialogNegativeClick(pathFile: Uri?): Boolean
|
||||
}
|
||||
|
||||
override fun onAttach(activity: Context?) {
|
||||
super.onAttach(activity)
|
||||
try {
|
||||
mDefinePathDialogListener = activity as DefinePathDialogListener?
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(activity?.toString()
|
||||
+ " must implement " + DefinePathDialogListener::class.java.name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
val inflater = activity.layoutInflater
|
||||
|
||||
val rootView = inflater.inflate(R.layout.fragment_file_creation, null)
|
||||
builder.setView(rootView)
|
||||
.setTitle(R.string.create_keepass_file)
|
||||
// Add action buttons
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
|
||||
// To prevent crash issue #69 https://github.com/Kunzisoft/KeePassDX/issues/69
|
||||
val actionCopyBarCallback = object : ActionMode.Callback {
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
positiveButton?.isEnabled = false
|
||||
negativeButton?.isEnabled = false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
positiveButton?.isEnabled = true
|
||||
negativeButton?.isEnabled = true
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Folder selection
|
||||
val browseView = rootView.findViewById<View>(R.id.browse_button)
|
||||
folderPathView = rootView.findViewById(R.id.folder_path)
|
||||
folderPathView?.customSelectionActionModeCallback = actionCopyBarCallback
|
||||
fileNameView = rootView.findViewById(R.id.filename)
|
||||
fileNameView?.customSelectionActionModeCallback = actionCopyBarCallback
|
||||
|
||||
val defaultPath = Environment.getExternalStorageDirectory().path + getString(R.string.database_file_path_default)
|
||||
folderPathView?.setText(defaultPath)
|
||||
browseView.setOnClickListener { _ ->
|
||||
Intent(context, FilePickerStylishActivity::class.java).apply {
|
||||
putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
||||
putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
||||
putExtra(FilePickerActivity.EXTRA_START_PATH,
|
||||
Environment.getExternalStorageDirectory().path)
|
||||
startActivityForResult(this, FILE_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
// Init path
|
||||
mUriPath = null
|
||||
|
||||
// Extension
|
||||
mDatabaseFileExtension = getString(R.string.database_file_extension_default)
|
||||
val spinner = rootView.findViewById<Spinner>(R.id.file_types)
|
||||
spinner.onItemSelectedListener = this
|
||||
|
||||
// Spinner Drop down elements
|
||||
val fileTypes = resources.getStringArray(R.array.file_types)
|
||||
val dataAdapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, fileTypes)
|
||||
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spinner.adapter = dataAdapter
|
||||
// Or text if only one item https://github.com/Kunzisoft/KeePassDX/issues/105
|
||||
if (fileTypes.size == 1) {
|
||||
val params = spinner.layoutParams
|
||||
spinner.visibility = View.GONE
|
||||
val extensionTextView = TextView(context)
|
||||
extensionTextView.text = mDatabaseFileExtension
|
||||
extensionTextView.layoutParams = params
|
||||
val parentView = spinner.parent as ViewGroup
|
||||
parentView.addView(extensionTextView)
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
dialog.setOnShowListener { _ ->
|
||||
positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
positiveButton?.setOnClickListener { _ ->
|
||||
mDefinePathDialogListener?.let {
|
||||
if (it.onDefinePathDialogPositiveClick(buildPath()))
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
negativeButton?.setOnClickListener { _->
|
||||
mDefinePathDialogListener?.let {
|
||||
if (it.onDefinePathDialogNegativeClick(buildPath())) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun buildPath(): Uri? {
|
||||
if (folderPathView != null && fileNameView != null && mDatabaseFileExtension != null) {
|
||||
var path = Uri.Builder().path(folderPathView!!.text.toString())
|
||||
.appendPath(fileNameView!!.text.toString() + mDatabaseFileExtension!!)
|
||||
.build()
|
||||
context?.let { context ->
|
||||
path = UriUtil.translateUri(context, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == FILE_CODE && resultCode == Activity.RESULT_OK) {
|
||||
mUriPath = data?.data
|
||||
mUriPath?.let {
|
||||
val file = Utils.getFileForUri(it)
|
||||
folderPathView?.setText(file.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemSelected(adapterView: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||
mDatabaseFileExtension = adapterView.getItemAtPosition(position).toString()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
|
||||
|
||||
class DeleteNodesDialogFragment : DialogFragment() {
|
||||
|
||||
private var mNodesToDelete: List<Node> = ArrayList()
|
||||
private var mListener: DeleteNodeListener? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
mListener = context as DeleteNodeListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + DeleteNodeListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
arguments?.apply {
|
||||
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
|
||||
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
|
||||
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), this)
|
||||
}
|
||||
} ?: savedInstanceState?.apply {
|
||||
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
|
||||
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
|
||||
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), savedInstanceState)
|
||||
}
|
||||
}
|
||||
activity?.let { activity ->
|
||||
// Use the Builder class for convenient dialog construction
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
builder.setMessage(getString(R.string.warning_permanently_delete_nodes))
|
||||
builder.setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
mListener?.permanentlyDeleteNodes(mNodesToDelete)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.no) { _, _ -> dismiss() }
|
||||
// Create the AlertDialog object and return it
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putAll(getBundleFromListNodes(mNodesToDelete))
|
||||
}
|
||||
|
||||
interface DeleteNodeListener {
|
||||
fun permanentlyDeleteNodes(nodes: List<Node>)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(nodesToDelete: List<Node>): DeleteNodesDialogFragment {
|
||||
return DeleteNodesDialogFragment().apply {
|
||||
arguments = getBundleFromListNodes(nodesToDelete)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class DuplicateUuidDialog : DialogFragment() {
|
||||
|
||||
var positiveAction: (() -> Unit)? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
// Use the Builder class for convenient dialog construction
|
||||
val builder = androidx.appcompat.app.AlertDialog.Builder(activity).apply {
|
||||
val message = getString(R.string.contains_duplicate_uuid) +
|
||||
"\n\n" + getString(R.string.contains_duplicate_uuid_procedure)
|
||||
setMessage(message)
|
||||
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
|
||||
positiveAction?.invoke()
|
||||
dismiss()
|
||||
}
|
||||
setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
|
||||
}
|
||||
// Create the AlertDialog object and return it
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
this.dismiss()
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
||||
import java.text.DateFormat
|
||||
|
||||
class FileInformationDialogFragment : DialogFragment() {
|
||||
|
||||
private var fileSizeContainerView: View? = null
|
||||
private var fileModificationContainerView: View? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
val inflater = activity.layoutInflater
|
||||
val root = inflater.inflate(R.layout.fragment_file_selection_information, null)
|
||||
val fileNameView = root.findViewById<TextView>(R.id.file_filename)
|
||||
val filePathView = root.findViewById<TextView>(R.id.file_path)
|
||||
fileSizeContainerView = root.findViewById(R.id.file_size_container)
|
||||
val fileSizeView = root.findViewById<TextView>(R.id.file_size)
|
||||
fileModificationContainerView = root.findViewById(R.id.file_modification_container)
|
||||
val fileModificationView = root.findViewById<TextView>(R.id.file_modification)
|
||||
|
||||
arguments?.apply {
|
||||
if (containsKey(FILE_SELECT_BEEN_ARG)) {
|
||||
(getSerializable(FILE_SELECT_BEEN_ARG) as FileDatabaseModel?)?.let { fileDatabaseModel ->
|
||||
fileDatabaseModel.fileUri?.let { fileUri ->
|
||||
filePathView.text = Uri.decode(fileUri.toString())
|
||||
}
|
||||
fileNameView.text = fileDatabaseModel.fileName
|
||||
|
||||
if (fileDatabaseModel.notFound()) {
|
||||
hideFileInfo()
|
||||
} else {
|
||||
showFileInfo()
|
||||
fileSizeView.text = fileDatabaseModel.size.toString()
|
||||
fileModificationView.text = DateFormat.getDateTimeInstance()
|
||||
.format(fileDatabaseModel.lastModification)
|
||||
}
|
||||
} ?: hideFileInfo()
|
||||
}
|
||||
}
|
||||
|
||||
builder.setView(root)
|
||||
builder.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun showFileInfo() {
|
||||
fileSizeContainerView?.visibility = View.VISIBLE
|
||||
fileModificationContainerView?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun hideFileInfo() {
|
||||
fileSizeContainerView?.visibility = View.GONE
|
||||
fileModificationContainerView?.visibility = View.GONE
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG"
|
||||
|
||||
fun newInstance(fileDatabaseModel: FileDatabaseModel): FileInformationDialogFragment {
|
||||
val fileInformationDialogFragment = FileInformationDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putSerializable(FILE_SELECT_BEEN_ARG, fileDatabaseModel)
|
||||
fileInformationDialogFragment.arguments = args
|
||||
return fileInformationDialogFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
@@ -22,14 +22,18 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.SeekBar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.applyFontVisibility
|
||||
import com.kunzisoft.keepass.view.applyFontVisibility
|
||||
|
||||
class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
|
||||
@@ -37,6 +41,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
|
||||
private var root: View? = null
|
||||
private var lengthTextView: EditText? = null
|
||||
private var passwordInputLayoutView: TextInputLayout? = null
|
||||
private var passwordView: EditText? = null
|
||||
|
||||
private var uppercaseBox: CompoundButton? = null
|
||||
@@ -49,12 +54,12 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
private var bracketsBox: CompoundButton? = null
|
||||
private var extendedBox: CompoundButton? = null
|
||||
|
||||
override fun onAttach(context: Context?) {
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
mListener = context as GeneratePasswordListener?
|
||||
mListener = context as GeneratePasswordListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context?.toString()
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + GeneratePasswordListener::class.java.name)
|
||||
}
|
||||
}
|
||||
@@ -65,6 +70,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
val inflater = activity.layoutInflater
|
||||
root = inflater.inflate(R.layout.fragment_generate_password, null)
|
||||
|
||||
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
|
||||
passwordView = root?.findViewById(R.id.password)
|
||||
passwordView?.applyFontVisibility()
|
||||
|
||||
@@ -108,7 +114,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
|
||||
dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
val bundle = Bundle()
|
||||
mListener?.cancelPassword(bundle)
|
||||
|
||||
@@ -162,8 +168,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
try {
|
||||
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
|
||||
|
||||
val generator = PasswordGenerator(resources)
|
||||
password = generator.generatePassword(length,
|
||||
password = PasswordGenerator(resources).generatePassword(length,
|
||||
uppercaseBox?.isChecked == true,
|
||||
lowercaseBox?.isChecked == true,
|
||||
digitsBox?.isChecked == true,
|
||||
@@ -174,9 +179,9 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
bracketsBox?.isChecked == true,
|
||||
extendedBox?.isChecked == true)
|
||||
} catch (e: NumberFormatException) {
|
||||
Toast.makeText(context, R.string.error_wrong_length, Toast.LENGTH_LONG).show()
|
||||
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||
passwordInputLayoutView?.error = e.message
|
||||
}
|
||||
|
||||
return password
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
@@ -23,9 +23,9 @@ import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.TextInputLayout
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
@@ -33,8 +33,8 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
|
||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
||||
import com.kunzisoft.keepass.database.element.PwIcon
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
|
||||
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
|
||||
@@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
||||
|
||||
private var editGroupDialogAction: EditGroupDialogAction? = null
|
||||
private var nameGroup: String? = null
|
||||
private var iconGroup: PwIcon? = null
|
||||
private var iconGroup: IconImage? = null
|
||||
|
||||
private var nameTextLayoutView: TextInputLayout? = null
|
||||
private var nameTextView: TextView? = null
|
||||
@@ -62,15 +62,15 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context?) {
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
// Verify that the host activity implements the callback interface
|
||||
try {
|
||||
// Instantiate the NoticeDialogListener so we can send events to the host
|
||||
editGroupListener = context as EditGroupListener?
|
||||
editGroupListener = context as EditGroupListener
|
||||
} catch (e: ClassCastException) {
|
||||
// The activity doesn't implement the interface, throw exception
|
||||
throw ClassCastException(context?.toString()
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + GroupEditDialogFragment::class.java.name)
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.setView(root)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
editGroupListener?.cancelEditGroup(
|
||||
editGroupDialogAction,
|
||||
nameTextView?.text?.toString(),
|
||||
@@ -186,8 +186,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
||||
}
|
||||
|
||||
interface EditGroupListener {
|
||||
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
|
||||
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
|
||||
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
|
||||
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -206,7 +206,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
||||
return fragment
|
||||
}
|
||||
|
||||
fun build(group: GroupVersioned): GroupEditDialogFragment {
|
||||
fun build(group: Group): GroupEditDialogFragment {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_NAME, group.title)
|
||||
bundle.putParcelable(KEY_ICON, group.icon)
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
@@ -24,9 +24,9 @@ import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v4.widget.ImageViewCompat
|
||||
import android.support.v7.app.AlertDialog
|
||||
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
|
||||
@@ -35,7 +35,7 @@ import android.widget.GridView
|
||||
import android.widget.ImageView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.database.element.PwIconStandard
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.icons.IconPack
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
|
||||
@@ -45,13 +45,13 @@ class IconPickerDialogFragment : DialogFragment() {
|
||||
private var iconPickerListener: IconPickerListener? = null
|
||||
private var iconPack: IconPack? = null
|
||||
|
||||
override fun onAttach(context: Context?) {
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
iconPickerListener = context as IconPickerListener?
|
||||
iconPickerListener = context as IconPickerListener
|
||||
} catch (e: ClassCastException) {
|
||||
// The activity doesn't implement the interface, throw exception
|
||||
throw ClassCastException(context!!.toString()
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + IconPickerListener::class.java.name)
|
||||
}
|
||||
}
|
||||
@@ -72,12 +72,12 @@ class IconPickerDialogFragment : DialogFragment() {
|
||||
|
||||
currIconGridView.setOnItemClickListener { _, _, position, _ ->
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position))
|
||||
bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
|
||||
iconPickerListener?.iconPicked(bundle)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
builder.setNegativeButton(R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog.cancel() }
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog?.cancel() }
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
@@ -128,7 +128,7 @@ class IconPickerDialogFragment : DialogFragment() {
|
||||
|
||||
private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD"
|
||||
|
||||
fun getIconStandardFromBundle(bundle: Bundle): PwIconStandard? {
|
||||
fun getIconStandardFromBundle(bundle: Bundle): IconImageStandard? {
|
||||
return bundle.getParcelable(KEY_ICON_STANDARD)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
|
||||
class KeyboardExplanationDialogFragment : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let {
|
||||
val builder = AlertDialog.Builder(activity!!)
|
||||
val inflater = activity!!.layoutInflater
|
||||
|
||||
val rootView = inflater.inflate(R.layout.fragment_keyboard_explanation, null)
|
||||
|
||||
rootView.findViewById<View>(R.id.keyboards_activate_setting_path1_text)
|
||||
.setOnClickListener { launchActivateKeyboardSetting() }
|
||||
rootView.findViewById<View>(R.id.keyboards_activate_setting_path2_text)
|
||||
.setOnClickListener { launchActivateKeyboardSetting() }
|
||||
|
||||
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
|
||||
} else {
|
||||
containerKeyboardSwitcher.setOnClickListener { Util.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
|
||||
}
|
||||
|
||||
builder.setView(rootView)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun launchActivateKeyboardSetting() {
|
||||
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
|
||||
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
@@ -23,7 +23,7 @@ import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class PasswordEncodingDialogFragment : DialogFragment() {
|
||||
@@ -35,7 +35,7 @@ class PasswordEncodingDialogFragment : DialogFragment() {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
|
||||
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
|
||||
builder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.cancel() }
|
||||
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.Html
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.widget.Toast
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
/**
|
||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||
@@ -44,25 +42,16 @@ class ProFeatureDialogFragment : DialogFragment() {
|
||||
|
||||
val stringBuilder = SpannableStringBuilder()
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
// TODO HtmlCompat with androidX
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_ad_free))).append("\n\n")
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_buy_pro)))
|
||||
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) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.app_pro_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_feature_generosity))).append("\n\n")
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_donation)))
|
||||
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) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.contribution_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.contribution_url)
|
||||
}
|
||||
}
|
||||
builder.setMessage(stringBuilder)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class ReadOnlyDialog(context: Context) : AlertDialog(context) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle) {
|
||||
val ctx = context
|
||||
var warning = ctx.getString(R.string.read_only_warning)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
warning = warning + "\n\n" + context.getString(R.string.read_only_kitkat_warning)
|
||||
}
|
||||
setMessage(warning)
|
||||
|
||||
setButton(BUTTON_POSITIVE, ctx.getText(android.R.string.ok)) { _, _ -> dismiss() }
|
||||
setButton(BUTTON_NEGATIVE, ctx.getText(R.string.beta_dontask)) { _, _ ->
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
val edit = prefs.edit()
|
||||
edit.putBoolean(ctx.getString(R.string.show_read_only_warning), false)
|
||||
edit.apply()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.Spinner
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.OtpModel
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_HOTP_COUNTER
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_OTP_DIGITS
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_TOTP_PERIOD
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_HOTP_COUNTER
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_OTP_DIGITS
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD
|
||||
import com.kunzisoft.keepass.otp.OtpTokenType
|
||||
import com.kunzisoft.keepass.otp.OtpType
|
||||
import com.kunzisoft.keepass.otp.TokenCalculator
|
||||
import java.util.*
|
||||
|
||||
class SetOTPDialogFragment : DialogFragment() {
|
||||
|
||||
private var mCreateOTPElementListener: CreateOtpListener? = null
|
||||
|
||||
private var mOtpElement: OtpElement = OtpElement()
|
||||
|
||||
private var otpTypeSpinner: Spinner? = null
|
||||
private var otpTokenTypeSpinner: Spinner? = null
|
||||
private var otpSecretContainer: TextInputLayout? = null
|
||||
private var otpSecretTextView: EditText? = null
|
||||
private var otpPeriodContainer: TextInputLayout? = null
|
||||
private var otpPeriodTextView: EditText? = null
|
||||
private var otpCounterContainer: TextInputLayout? = null
|
||||
private var otpCounterTextView: EditText? = null
|
||||
private var otpDigitsContainer: TextInputLayout? = null
|
||||
private var otpDigitsTextView: EditText? = null
|
||||
private var otpAlgorithmSpinner: Spinner? = null
|
||||
|
||||
private var otpTypeAdapter: ArrayAdapter<OtpType>? = null
|
||||
private var otpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||
private var totpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||
private var hotpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
|
||||
private var otpAlgorithmAdapter: ArrayAdapter<TokenCalculator.HashAlgorithm>? = null
|
||||
|
||||
private var mManualEvent = false
|
||||
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
|
||||
if (!isFocus)
|
||||
mManualEvent = true
|
||||
}
|
||||
private var mOnTouchListener = View.OnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
mManualEvent = true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
private var mSecretWellFormed = false
|
||||
private var mCounterWellFormed = true
|
||||
private var mPeriodWellFormed = true
|
||||
private var mDigitsWellFormed = true
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
// Verify that the host activity implements the callback interface
|
||||
try {
|
||||
// Instantiate the NoticeDialogListener so we can send events to the host
|
||||
mCreateOTPElementListener = context as CreateOtpListener
|
||||
} catch (e: ClassCastException) {
|
||||
// The activity doesn't implement the interface, throw exception
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + CreateOtpListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
// Retrieve OTP model from instance state
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.containsKey(KEY_OTP)) {
|
||||
savedInstanceState.getParcelable<OtpModel>(KEY_OTP)?.let { otpModel ->
|
||||
mOtpElement = OtpElement(otpModel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
arguments?.apply {
|
||||
if (containsKey(KEY_OTP)) {
|
||||
getParcelable<OtpModel?>(KEY_OTP)?.let { otpModel ->
|
||||
mOtpElement = OtpElement(otpModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity?.let { activity ->
|
||||
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
|
||||
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
|
||||
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
|
||||
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
|
||||
otpSecretTextView = root?.findViewById(R.id.setup_otp_secret)
|
||||
otpAlgorithmSpinner = root?.findViewById(R.id.setup_otp_algorithm)
|
||||
otpPeriodContainer= root?.findViewById(R.id.setup_otp_period_label)
|
||||
otpPeriodTextView = root?.findViewById(R.id.setup_otp_period)
|
||||
otpCounterContainer= root?.findViewById(R.id.setup_otp_counter_label)
|
||||
otpCounterTextView = root?.findViewById(R.id.setup_otp_counter)
|
||||
otpDigitsContainer = root?.findViewById(R.id.setup_otp_digits_label)
|
||||
otpDigitsTextView = root?.findViewById(R.id.setup_otp_digits)
|
||||
|
||||
// To fix init element
|
||||
// With tab keyboard selection
|
||||
otpSecretTextView?.onFocusChangeListener = mOnFocusChangeListener
|
||||
// With finger selection
|
||||
otpTypeSpinner?.setOnTouchListener(mOnTouchListener)
|
||||
otpTokenTypeSpinner?.setOnTouchListener(mOnTouchListener)
|
||||
otpSecretTextView?.setOnTouchListener(mOnTouchListener)
|
||||
otpAlgorithmSpinner?.setOnTouchListener(mOnTouchListener)
|
||||
otpPeriodTextView?.setOnTouchListener(mOnTouchListener)
|
||||
otpCounterTextView?.setOnTouchListener(mOnTouchListener)
|
||||
otpDigitsTextView?.setOnTouchListener(mOnTouchListener)
|
||||
|
||||
|
||||
// HOTP / TOTP Type selection
|
||||
val otpTypeArray = OtpType.values()
|
||||
otpTypeAdapter = ArrayAdapter<OtpType>(activity,
|
||||
android.R.layout.simple_spinner_item, otpTypeArray).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
otpTypeSpinner?.adapter = otpTypeAdapter
|
||||
|
||||
// Otp Token type selection
|
||||
val hotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
|
||||
hotpTokenTypeAdapter = ArrayAdapter(activity,
|
||||
android.R.layout.simple_spinner_item, hotpTokenTypeArray).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
// Proprietary only on closed and full version
|
||||
val totpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
|
||||
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
|
||||
totpTokenTypeAdapter = ArrayAdapter(activity,
|
||||
android.R.layout.simple_spinner_item, totpTokenTypeArray).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
otpTokenTypeAdapter = hotpTokenTypeAdapter
|
||||
otpTokenTypeSpinner?.adapter = otpTokenTypeAdapter
|
||||
|
||||
// OTP Algorithm
|
||||
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
|
||||
otpAlgorithmAdapter = ArrayAdapter<TokenCalculator.HashAlgorithm>(activity,
|
||||
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
otpAlgorithmSpinner?.adapter = otpAlgorithmAdapter
|
||||
|
||||
// Set the default value of OTP element
|
||||
upgradeType()
|
||||
upgradeTokenType()
|
||||
upgradeParameters()
|
||||
|
||||
attachListeners()
|
||||
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.apply {
|
||||
setTitle(R.string.entry_setup_otp)
|
||||
setView(root)
|
||||
.setPositiveButton(android.R.string.ok) {_, _ -> }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
}
|
||||
}
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(dialog as AlertDialog).getButton(Dialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
if (mSecretWellFormed
|
||||
&& mCounterWellFormed
|
||||
&& mPeriodWellFormed
|
||||
&& mDigitsWellFormed) {
|
||||
mCreateOTPElementListener?.onOtpCreated(mOtpElement)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachListeners() {
|
||||
// Set Type listener
|
||||
otpTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
if (mManualEvent) {
|
||||
(parent?.selectedItem as OtpType?)?.let {
|
||||
mOtpElement.type = it
|
||||
upgradeTokenType()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set type token listener
|
||||
otpTokenTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
if (mManualEvent) {
|
||||
(parent?.selectedItem as OtpTokenType?)?.let {
|
||||
mOtpElement.tokenType = it
|
||||
upgradeParameters()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set algorithm spinner
|
||||
otpAlgorithmSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
if (mManualEvent) {
|
||||
(parent?.selectedItem as TokenCalculator.HashAlgorithm?)?.let {
|
||||
mOtpElement.algorithm = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set secret in OtpElement
|
||||
otpSecretTextView?.addTextChangedListener(object: TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
s?.toString()?.let { userString ->
|
||||
try {
|
||||
mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH))
|
||||
otpSecretContainer?.error = null
|
||||
} catch (exception: Exception) {
|
||||
otpSecretContainer?.error = getString(R.string.error_otp_secret_key)
|
||||
}
|
||||
mSecretWellFormed = otpSecretContainer?.error == null
|
||||
}
|
||||
}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
// Set counter in OtpElement
|
||||
otpCounterTextView?.addTextChangedListener(object: TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
if (mManualEvent) {
|
||||
s?.toString()?.toLongOrNull()?.let {
|
||||
try {
|
||||
mOtpElement.counter = it
|
||||
otpCounterContainer?.error = null
|
||||
} catch (exception: Exception) {
|
||||
otpCounterContainer?.error = getString(R.string.error_otp_counter,
|
||||
MIN_HOTP_COUNTER, MAX_HOTP_COUNTER)
|
||||
}
|
||||
mCounterWellFormed = otpCounterContainer?.error == null
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
// Set period in OtpElement
|
||||
otpPeriodTextView?.addTextChangedListener(object: TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
if (mManualEvent) {
|
||||
s?.toString()?.toIntOrNull()?.let {
|
||||
try {
|
||||
mOtpElement.period = it
|
||||
otpPeriodContainer?.error = null
|
||||
} catch (exception: Exception) {
|
||||
otpPeriodContainer?.error = getString(R.string.error_otp_period,
|
||||
MIN_TOTP_PERIOD, MAX_TOTP_PERIOD)
|
||||
}
|
||||
mPeriodWellFormed = otpPeriodContainer?.error == null
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
// Set digits in OtpElement
|
||||
otpDigitsTextView?.addTextChangedListener(object: TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
if (mManualEvent) {
|
||||
s?.toString()?.toIntOrNull()?.let {
|
||||
try {
|
||||
mOtpElement.digits = it
|
||||
otpDigitsContainer?.error = null
|
||||
} catch (exception: Exception) {
|
||||
otpDigitsContainer?.error = getString(R.string.error_otp_digits,
|
||||
MIN_OTP_DIGITS, MAX_OTP_DIGITS)
|
||||
}
|
||||
mDigitsWellFormed = otpDigitsContainer?.error == null
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
}
|
||||
|
||||
private fun upgradeType() {
|
||||
otpTypeSpinner?.setSelection(OtpType.values().indexOf(mOtpElement.type))
|
||||
}
|
||||
|
||||
private fun upgradeTokenType() {
|
||||
when (mOtpElement.type) {
|
||||
OtpType.HOTP -> {
|
||||
otpPeriodContainer?.visibility = View.GONE
|
||||
otpCounterContainer?.visibility = View.VISIBLE
|
||||
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
|
||||
otpTokenTypeSpinner?.setSelection(OtpTokenType
|
||||
.getHotpTokenTypeValues().indexOf(mOtpElement.tokenType))
|
||||
}
|
||||
OtpType.TOTP -> {
|
||||
otpPeriodContainer?.visibility = View.VISIBLE
|
||||
otpCounterContainer?.visibility = View.GONE
|
||||
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
|
||||
otpTokenTypeSpinner?.setSelection(OtpTokenType
|
||||
.getTotpTokenTypeValues().indexOf(mOtpElement.tokenType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun upgradeParameters() {
|
||||
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
|
||||
.indexOf(mOtpElement.algorithm))
|
||||
otpSecretTextView?.apply {
|
||||
setText(mOtpElement.getBase32Secret())
|
||||
// Cursor at end
|
||||
setSelection(this.text.length)
|
||||
}
|
||||
otpCounterTextView?.setText(mOtpElement.counter.toString())
|
||||
otpPeriodTextView?.setText(mOtpElement.period.toString())
|
||||
otpDigitsTextView?.setText(mOtpElement.digits.toString())
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putParcelable(KEY_OTP, mOtpElement.otpModel)
|
||||
}
|
||||
|
||||
interface CreateOtpListener {
|
||||
fun onOtpCreated(otpElement: OtpElement)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val KEY_OTP = "KEY_OTP"
|
||||
|
||||
fun build(otpModel: OtpModel? = null): SetOTPDialogFragment {
|
||||
return SetOTPDialogFragment().apply {
|
||||
if (otpModel != null) {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(KEY_OTP, otpModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
@@ -22,46 +22,43 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.IdRes
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.RadioGroup
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
|
||||
class SortDialogFragment : DialogFragment() {
|
||||
|
||||
private var mListener: SortSelectionListener? = null
|
||||
|
||||
private var mSortNodeEnum: SortNodeEnum? = null
|
||||
private var mSortNodeEnum: SortNodeEnum = SortNodeEnum.DB
|
||||
@IdRes
|
||||
private var mCheckedId: Int = 0
|
||||
private var mGroupsBefore: Boolean = false
|
||||
private var mAscending: Boolean = false
|
||||
private var mRecycleBinBottom: Boolean = false
|
||||
private var mGroupsBefore: Boolean = true
|
||||
private var mAscending: Boolean = true
|
||||
private var mRecycleBinBottom: Boolean = true
|
||||
|
||||
override fun onAttach(context: Context?) {
|
||||
private var recycleBinBottomView: CompoundButton? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
mListener = context as SortSelectionListener?
|
||||
mListener = context as SortSelectionListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context!!.toString()
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + SortSelectionListener::class.java.name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
mSortNodeEnum = SortNodeEnum.TITLE
|
||||
mAscending = true
|
||||
mGroupsBefore = true
|
||||
var recycleBinAllowed = false
|
||||
mRecycleBinBottom = true
|
||||
|
||||
arguments?.apply {
|
||||
if (containsKey(SORT_NODE_ENUM_BUNDLE_KEY))
|
||||
@@ -78,15 +75,15 @@ class SortDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
mCheckedId = retrieveViewFromEnum(mSortNodeEnum!!)
|
||||
mCheckedId = retrieveViewFromEnum(mSortNodeEnum)
|
||||
|
||||
val rootView = activity.layoutInflater.inflate(R.layout.fragment_sort_selection, null)
|
||||
builder.setTitle(R.string.sort_menu)
|
||||
builder.setView(rootView)
|
||||
// Add action buttons
|
||||
.setPositiveButton(android.R.string.ok
|
||||
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum!!, mAscending, mGroupsBefore, mRecycleBinBottom) }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
|
||||
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
|
||||
// Check if is ascending or descending
|
||||
@@ -98,25 +95,35 @@ class SortDialogFragment : DialogFragment() {
|
||||
groupsBeforeView.isChecked = mGroupsBefore
|
||||
groupsBeforeView.setOnCheckedChangeListener { _, isChecked -> mGroupsBefore = isChecked }
|
||||
|
||||
val recycleBinBottomView = rootView.findViewById<CompoundButton>(R.id.sort_selection_recycle_bin_bottom)
|
||||
recycleBinBottomView = rootView.findViewById(R.id.sort_selection_recycle_bin_bottom)
|
||||
if (!recycleBinAllowed) {
|
||||
recycleBinBottomView.visibility = View.GONE
|
||||
recycleBinBottomView?.visibility = View.GONE
|
||||
} else {
|
||||
// Check if recycle bin at the bottom
|
||||
recycleBinBottomView.isChecked = mRecycleBinBottom
|
||||
recycleBinBottomView.setOnCheckedChangeListener { _, isChecked -> mRecycleBinBottom = isChecked }
|
||||
recycleBinBottomView?.isChecked = mRecycleBinBottom
|
||||
recycleBinBottomView?.setOnCheckedChangeListener { _, isChecked -> mRecycleBinBottom = isChecked }
|
||||
|
||||
disableRecycleBinBottomOptionIfNaturalOrder()
|
||||
}
|
||||
|
||||
val sortSelectionRadioGroupView = rootView.findViewById<RadioGroup>(R.id.sort_selection_radio_group)
|
||||
// Check value by default
|
||||
sortSelectionRadioGroupView.check(mCheckedId)
|
||||
sortSelectionRadioGroupView.setOnCheckedChangeListener { _, checkedId -> mSortNodeEnum = retrieveSortEnumFromViewId(checkedId) }
|
||||
sortSelectionRadioGroupView.setOnCheckedChangeListener { _, checkedId ->
|
||||
mSortNodeEnum = retrieveSortEnumFromViewId(checkedId)
|
||||
disableRecycleBinBottomOptionIfNaturalOrder()
|
||||
}
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun disableRecycleBinBottomOptionIfNaturalOrder() {
|
||||
// Disable recycle bin if natural order
|
||||
recycleBinBottomView?.isEnabled = mSortNodeEnum != SortNodeEnum.DB
|
||||
}
|
||||
|
||||
@IdRes
|
||||
private fun retrieveViewFromEnum(sortNodeEnum: SortNodeEnum): Int {
|
||||
return when (sortNodeEnum) {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
@@ -22,12 +22,13 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.text.Html
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class UnavailableFeatureDialogFragment : DialogFragment() {
|
||||
@@ -53,7 +54,7 @@ class UnavailableFeatureDialogFragment : DialogFragment() {
|
||||
androidNameFromApiNumber(Build.VERSION.SDK_INT, Build.VERSION.RELEASE),
|
||||
androidNameFromApiNumber(minVersionRequired)))
|
||||
message.append("\n\n")
|
||||
.append(Html.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>"))
|
||||
.append(HtmlCompat.fromHtml("<a href=\"https://source.android.com/setup/build-numbers\">CodeNames</a>", HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||
} else
|
||||
message.append(getString(R.string.unavailable_feature_hardware))
|
||||
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.Html
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.widget.Toast
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Util
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
/**
|
||||
* Custom Dialog that asks the user to download the pro version or make a donation.
|
||||
@@ -45,34 +42,26 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
|
||||
val stringBuilder = SpannableStringBuilder()
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
if (BuildConfig.FULL_VERSION) {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature_thanks))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_rose))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_work_hard))).append("\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_upgrade))).append(" ")
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
||||
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
|
||||
} else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_buy_pro))).append("\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||
builder.setPositiveButton(R.string.download) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.app_pro_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||
}
|
||||
} else {
|
||||
stringBuilder.append(Html.fromHtml(getString(R.string.html_text_dev_feature))).append("\n\n")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_contibute))).append(" ")
|
||||
.append(Html.fromHtml(getString(R.string.html_text_dev_feature_encourage)))
|
||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
||||
try {
|
||||
Util.gotoUrl(context!!, R.string.contribution_url)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.error_failed_to_launch_link, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
UriUtil.gotoUrl(context!!, R.string.contribution_url)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.helpers;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ClipDataCompat {
|
||||
private static Method getClipDataFromIntent;
|
||||
private static Method getDescription;
|
||||
private static Method getItemCount;
|
||||
private static Method getLabel;
|
||||
private static Method getItemAt;
|
||||
private static Method getUri;
|
||||
|
||||
private static boolean initSucceded;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class clipData = Class.forName("android.content.ClipData");
|
||||
getDescription = clipData.getMethod("getDescription", (Class[])null);
|
||||
getItemCount = clipData.getMethod("getItemCount", (Class[])null);
|
||||
getItemAt = clipData.getMethod("getItemAt", new Class[]{int.class});
|
||||
Class clipDescription = Class.forName("android.content.ClipDescription");
|
||||
getLabel = clipDescription.getMethod("getLabel", (Class[])null);
|
||||
|
||||
Class clipDataItem = Class.forName("android.content.ClipData$Item");
|
||||
getUri = clipDataItem.getMethod("getUri", (Class[])null);
|
||||
|
||||
getClipDataFromIntent = Intent.class.getMethod("getClipData", (Class[])null);
|
||||
|
||||
initSucceded = true;
|
||||
} catch (Exception e) {
|
||||
initSucceded = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Uri getUriFromIntent(Intent i, String key) {
|
||||
if (initSucceded) {
|
||||
try {
|
||||
Object clip = getClipDataFromIntent.invoke(i);
|
||||
|
||||
if (clip != null) {
|
||||
Object clipDescription = getDescription.invoke(clip);
|
||||
CharSequence label = (CharSequence)getLabel.invoke(clipDescription);
|
||||
if (label.equals(key)) {
|
||||
int itemCount = (int) getItemCount.invoke(clip);
|
||||
if (itemCount == 1) {
|
||||
Object clipItem = getItemAt.invoke(clip,0);
|
||||
if (clipItem != null) {
|
||||
return (Uri)getUri.invoke(clipItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Fall through below to backup method if reflection fails
|
||||
}
|
||||
}
|
||||
|
||||
return i.getParcelableExtra(key);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.helpers
|
||||
|
||||
import android.app.assist.AssistStructure
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
@@ -10,6 +30,12 @@ object EntrySelectionHelper {
|
||||
private const val EXTRA_ENTRY_SELECTION_MODE = "com.kunzisoft.keepass.extra.ENTRY_SELECTION_MODE"
|
||||
private const val DEFAULT_ENTRY_SELECTION_MODE = false
|
||||
|
||||
fun startActivityForEntrySelection(context: Context, intent: Intent) {
|
||||
addEntrySelectionModeExtraInIntent(intent)
|
||||
// only to avoid visible flickering when redirecting
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun addEntrySelectionModeExtraInIntent(intent: Intent) {
|
||||
intent.putExtra(EXTRA_ENTRY_SELECTION_MODE, true)
|
||||
}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.helpers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Context
|
||||
@@ -26,21 +27,20 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentActivity
|
||||
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.fileselect.StorageAF
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class KeyFileHelper {
|
||||
class OpenFileHelper {
|
||||
|
||||
private var activity: Activity? = null
|
||||
private var fragment: Fragment? = null
|
||||
|
||||
val openFileOnClickViewListener: OpenFileOnClickViewListener
|
||||
get() = OpenFileOnClickViewListener(null)
|
||||
get() = OpenFileOnClickViewListener()
|
||||
|
||||
constructor(context: Activity) {
|
||||
this.activity = context
|
||||
@@ -52,69 +52,61 @@ class KeyFileHelper {
|
||||
this.fragment = context
|
||||
}
|
||||
|
||||
inner class OpenFileOnClickViewListener(private val dataUri: (() -> Uri)?) : View.OnClickListener {
|
||||
inner class OpenFileOnClickViewListener : View.OnClickListener {
|
||||
|
||||
override fun onClick(v: View) {
|
||||
try {
|
||||
if (activity != null && StorageAF.useStorageFramework(activity!!)) {
|
||||
try {
|
||||
openActivityWithActionOpenDocument()
|
||||
} else {
|
||||
} catch(e: Exception) {
|
||||
openActivityWithActionGetContent()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Enable to start the file picker activity", e)
|
||||
|
||||
// Open File picker if can't open activity
|
||||
if (lookForOpenIntentsFilePicker(dataUri?.invoke()))
|
||||
// Open browser dialog
|
||||
if (lookForOpenIntentsFilePicker())
|
||||
showBrowserDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun openActivityWithActionOpenDocument() {
|
||||
val i = Intent(StorageAF.ACTION_OPEN_DOCUMENT)
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
i.type = "*/*"
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
i.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
} else {
|
||||
i.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
}
|
||||
if (fragment != null)
|
||||
fragment?.startActivityForResult(i, OPEN_DOC)
|
||||
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
|
||||
else
|
||||
activity?.startActivityForResult(i, OPEN_DOC)
|
||||
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun openActivityWithActionGetContent() {
|
||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
i.type = "*/*"
|
||||
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
}
|
||||
if (fragment != null)
|
||||
fragment?.startActivityForResult(i, GET_CONTENT)
|
||||
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
|
||||
else
|
||||
activity?.startActivityForResult(i, GET_CONTENT)
|
||||
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
|
||||
}
|
||||
|
||||
fun getOpenFileOnClickViewListener(dataUri: () -> Uri): OpenFileOnClickViewListener {
|
||||
return OpenFileOnClickViewListener(dataUri)
|
||||
}
|
||||
|
||||
private fun lookForOpenIntentsFilePicker(dataUri: Uri?): Boolean {
|
||||
private fun lookForOpenIntentsFilePicker(): Boolean {
|
||||
var showBrowser = false
|
||||
try {
|
||||
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
|
||||
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
|
||||
// Get file path parent if possible
|
||||
if (dataUri != null
|
||||
&& dataUri.toString().isNotEmpty()
|
||||
&& dataUri.scheme == "file") {
|
||||
intent.data = dataUri
|
||||
} else {
|
||||
Log.w(javaClass.name, "Unable to read the URI")
|
||||
}
|
||||
if (fragment != null)
|
||||
fragment?.startActivityForResult(intent, FILE_BROWSE)
|
||||
else
|
||||
@@ -158,7 +150,7 @@ class KeyFileHelper {
|
||||
val browserDialogFragment = BrowserDialogFragment()
|
||||
if (fragment != null && fragment!!.fragmentManager != null)
|
||||
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
|
||||
else if (activity!!.fragmentManager != null)
|
||||
else
|
||||
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Can't open BrowserDialog", e)
|
||||
@@ -182,7 +174,7 @@ class KeyFileHelper {
|
||||
val filename = data?.dataString
|
||||
var keyUri: Uri? = null
|
||||
if (filename != null) {
|
||||
keyUri = UriUtil.parseUriFile(filename)
|
||||
keyUri = UriUtil.parse(filename)
|
||||
}
|
||||
keyFileCallback?.invoke(keyUri)
|
||||
}
|
||||
@@ -191,23 +183,18 @@ class KeyFileHelper {
|
||||
GET_CONTENT, OPEN_DOC -> {
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (data != null) {
|
||||
var uri = data.data
|
||||
val uri = data.data
|
||||
if (uri != null) {
|
||||
if (StorageAF.useStorageFramework(activity!!)) {
|
||||
try {
|
||||
// try to persist read and write permissions
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
activity?.contentResolver?.apply {
|
||||
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
try {
|
||||
// try to persist read and write permissions
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
activity?.contentResolver?.apply {
|
||||
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
if (requestCode == GET_CONTENT) {
|
||||
uri = UriUtil.translateUri(activity!!, uri)
|
||||
} catch (e: Exception) {
|
||||
// nop
|
||||
}
|
||||
keyFileCallback?.invoke(uri)
|
||||
}
|
||||
@@ -221,7 +208,13 @@ class KeyFileHelper {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "KeyFileHelper"
|
||||
private const val TAG = "OpenFileHelper"
|
||||
|
||||
private var APP_ACTION_OPEN_DOCUMENT: String = try {
|
||||
Intent::class.java.getField("ACTION_OPEN_DOCUMENT").get(null) as String
|
||||
} catch (e: Exception) {
|
||||
"android.intent.action.OPEN_DOCUMENT"
|
||||
}
|
||||
|
||||
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.helpers
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.kunzisoft.keepass.activities.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.fileselect.database.FileDatabaseHistory
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class UriIntentInitTask(private val weakContext: WeakReference<Context>,
|
||||
private val uriIntentInitTaskCallback: UriIntentInitTaskCallback,
|
||||
private val isKeyFileNeeded: Boolean)
|
||||
: AsyncTask<Intent, Void, Int>() {
|
||||
|
||||
private var databaseUri: Uri? = null
|
||||
private var keyFileUri: Uri? = null
|
||||
|
||||
override fun doInBackground(vararg args: Intent): Int? {
|
||||
val intent = args[0]
|
||||
val action = intent.action
|
||||
|
||||
// If is a view intent
|
||||
if (action != null && action == VIEW_INTENT) {
|
||||
val incoming = intent.data
|
||||
databaseUri = incoming
|
||||
keyFileUri = ClipDataCompat.getUriFromIntent(intent, KEY_KEYFILE)
|
||||
|
||||
if (incoming == null) {
|
||||
return R.string.error_can_not_handle_uri
|
||||
} else if (incoming.scheme == "file") {
|
||||
val fileName = incoming.path
|
||||
|
||||
if (fileName?.isNotEmpty() == true) {
|
||||
// No file name
|
||||
return R.string.file_not_found
|
||||
}
|
||||
|
||||
val dbFile = File(fileName)
|
||||
if (!dbFile.exists()) {
|
||||
// File does not exist
|
||||
return R.string.file_not_found
|
||||
}
|
||||
|
||||
if (keyFileUri == null) {
|
||||
keyFileUri = getKeyFileUri(databaseUri)
|
||||
}
|
||||
} else if (incoming.scheme == "content") {
|
||||
if (keyFileUri == null) {
|
||||
keyFileUri = getKeyFileUri(databaseUri)
|
||||
}
|
||||
} else {
|
||||
return R.string.error_can_not_handle_uri
|
||||
}
|
||||
|
||||
} else {
|
||||
databaseUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_FILENAME))
|
||||
keyFileUri = UriUtil.parseUriFile(intent.getStringExtra(KEY_KEYFILE))
|
||||
|
||||
if (keyFileUri == null || keyFileUri!!.toString().isEmpty()) {
|
||||
keyFileUri = getKeyFileUri(databaseUri)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public override fun onPostExecute(result: Int?) {
|
||||
uriIntentInitTaskCallback.onPostInitTask(databaseUri, keyFileUri, result)
|
||||
}
|
||||
|
||||
private fun getKeyFileUri(databaseUri: Uri?): Uri? {
|
||||
return if (isKeyFileNeeded) {
|
||||
FileDatabaseHistory.getInstance(weakContext).getKeyFileUriByDatabaseUri(databaseUri!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_FILENAME = "fileName"
|
||||
const val KEY_KEYFILE = "keyFile"
|
||||
private const val VIEW_INTENT = "android.intent.action.VIEW"
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.helpers
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
interface UriIntentInitTaskCallback {
|
||||
fun onPostInitTask(databaseFileUri: Uri?, keyFileUri: Uri?, errorStringId: Int?)
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.lock
|
||||
@@ -32,9 +32,11 @@ import android.view.ViewGroup
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||
@@ -51,46 +53,52 @@ abstract class LockingActivity : StylishActivity() {
|
||||
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
|
||||
}
|
||||
|
||||
protected var timeoutEnable: Boolean = true
|
||||
protected var mTimeoutEnable: Boolean = true
|
||||
|
||||
private var lockReceiver: LockReceiver? = null
|
||||
private var exitLock: Boolean = false
|
||||
private var mLockReceiver: LockReceiver? = null
|
||||
private var mExitLock: Boolean = false
|
||||
|
||||
// Force readOnly if Entry Selection mode
|
||||
protected var readOnly: Boolean = false
|
||||
protected var mReadOnly: Boolean = false
|
||||
get() {
|
||||
return field || selectionMode
|
||||
return field || mSelectionMode
|
||||
}
|
||||
protected var selectionMode: Boolean = false
|
||||
protected var mSelectionMode: Boolean = false
|
||||
protected var mAutoSaveEnable: Boolean = true
|
||||
|
||||
var mProgressDialogThread: ProgressDialogThread? = null
|
||||
private set
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)) {
|
||||
timeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
|
||||
mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
|
||||
} else {
|
||||
if (intent != null)
|
||||
timeoutEnable = intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
|
||||
mTimeoutEnable = intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
|
||||
}
|
||||
|
||||
if (timeoutEnable) {
|
||||
lockReceiver = LockReceiver()
|
||||
if (mTimeoutEnable) {
|
||||
mLockReceiver = LockReceiver()
|
||||
val intentFilter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_SCREEN_OFF)
|
||||
addAction(LOCK_ACTION)
|
||||
}
|
||||
registerReceiver(lockReceiver, intentFilter)
|
||||
registerReceiver(mLockReceiver, intentFilter)
|
||||
}
|
||||
|
||||
exitLock = false
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
|
||||
mExitLock = false
|
||||
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
|
||||
|
||||
mProgressDialogThread = ProgressDialogThread(this)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (resultCode == RESULT_EXIT_LOCK) {
|
||||
exitLock = true
|
||||
mExitLock = true
|
||||
if (Database.getInstance().loaded) {
|
||||
lockAndExit()
|
||||
}
|
||||
@@ -100,10 +108,15 @@ abstract class LockingActivity : StylishActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// To refresh when back to normal workflow from selection workflow
|
||||
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
|
||||
if (timeoutEnable) {
|
||||
// To refresh when back to normal workflow from selection workflow
|
||||
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
|
||||
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
|
||||
|
||||
invalidateOptionsMenu()
|
||||
|
||||
if (mTimeoutEnable) {
|
||||
// End activity if database not loaded
|
||||
if (!Database.getInstance().loaded) {
|
||||
finish()
|
||||
@@ -115,23 +128,23 @@ abstract class LockingActivity : StylishActivity() {
|
||||
// If the time is out -> close the Activity
|
||||
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
||||
// If onCreate already record time
|
||||
if (!exitLock)
|
||||
if (!mExitLock)
|
||||
TimeoutHelper.recordTime(this)
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
||||
outState.putBoolean(TIMEOUT_ENABLE_KEY, timeoutEnable)
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, mReadOnly)
|
||||
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mProgressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
|
||||
if (timeoutEnable) {
|
||||
if (mTimeoutEnable) {
|
||||
// If the time is out during our navigation in activity -> close the Activity
|
||||
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
||||
}
|
||||
@@ -139,8 +152,8 @@ abstract class LockingActivity : StylishActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (lockReceiver != null)
|
||||
unregisterReceiver(lockReceiver)
|
||||
if (mLockReceiver != null)
|
||||
unregisterReceiver(mLockReceiver)
|
||||
}
|
||||
|
||||
inner class LockReceiver : BroadcastReceiver() {
|
||||
@@ -184,7 +197,7 @@ abstract class LockingActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (timeoutEnable) {
|
||||
if (mTimeoutEnable) {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
@@ -199,6 +212,9 @@ fun Activity.lock() {
|
||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||
MagikIME.removeEntry(this)
|
||||
|
||||
// Stop the notification service
|
||||
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||
|
||||
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
|
||||
" after inactivity or manual lock")
|
||||
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.lock
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
|
||||
/**
|
||||
* Locking Hide Activity that sets FLAG_SECURE to prevent screenshots, and from
|
||||
* appearing in the recent app preview
|
||||
*/
|
||||
abstract class LockingHideActivity : LockingActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Several gingerbread devices have problems with FLAG_SECURE
|
||||
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
|
||||
/* (non-Javadoc) Workaround for HTC Linkify issues
|
||||
* @see android.app.Activity#startActivity(android.content.Intent)
|
||||
*/
|
||||
override fun startActivity(intent: Intent) {
|
||||
try {
|
||||
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
|
||||
intent.component = null
|
||||
}
|
||||
super.startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
/* Catch the bad HTC implementation case */
|
||||
super.startActivity(Intent.createChooser(intent, null))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.stylish
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.StyleRes
|
||||
import android.util.Log
|
||||
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
||||
|
||||
/**
|
||||
* FilePickerActivity class with a style compatibility
|
||||
*/
|
||||
class FilePickerStylishActivity : FilePickerActivity() {
|
||||
|
||||
@StyleRes
|
||||
private var themeId: Int = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
this.themeId = Stylish.getFilePickerThemeId(this)
|
||||
setTheme(themeId)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (Stylish.getFilePickerThemeId(this) != this.themeId) {
|
||||
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
|
||||
this.recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,27 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.stylish
|
||||
|
||||
import android.content.Context
|
||||
import android.support.annotation.StyleRes
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
@@ -61,6 +61,7 @@ object Stylish {
|
||||
|
||||
return when (themeString) {
|
||||
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
|
||||
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
|
||||
context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark
|
||||
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
|
||||
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red
|
||||
@@ -68,15 +69,4 @@ object Stylish {
|
||||
else -> R.style.KeepassDXStyle_Light
|
||||
}
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
fun getFilePickerThemeId(context: Context): Int {
|
||||
return when {
|
||||
themeString.equals(context.getString(R.string.list_style_name_dark)) -> R.style.KeepassDXStyle_FilePickerStyle_Dark
|
||||
themeString.equals(context.getString(R.string.list_style_name_blue)) -> R.style.KeepassDXStyle_FilePickerStyle_Blue
|
||||
themeString.equals(context.getString(R.string.list_style_name_red)) -> R.style.KeepassDXStyle_FilePickerStyle_Red
|
||||
themeString.equals(context.getString(R.string.list_style_name_purple)) -> R.style.KeepassDXStyle_FilePickerStyle_Purple
|
||||
else -> R.style.KeepassDXStyle_FilePickerStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,63 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.stylish
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.StyleRes
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
|
||||
/**
|
||||
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
|
||||
* appearing in the recent app preview
|
||||
*/
|
||||
abstract class StylishActivity : AppCompatActivity() {
|
||||
|
||||
@StyleRes
|
||||
private var themeId: Int = 0
|
||||
|
||||
/* (non-Javadoc) Workaround for HTC Linkify issues
|
||||
* @see android.app.Activity#startActivity(android.content.Intent)
|
||||
*/
|
||||
override fun startActivity(intent: Intent) {
|
||||
try {
|
||||
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
|
||||
intent.component = null
|
||||
}
|
||||
super.startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
/* Catch the bad HTC implementation case */
|
||||
super.startActivity(Intent.createChooser(intent, null))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
this.themeId = Stylish.getThemeId(this)
|
||||
setTheme(themeId)
|
||||
|
||||
// Several gingerbread devices have problems with FLAG_SECURE
|
||||
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.stylish
|
||||
@@ -23,9 +23,9 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.StyleRes
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.view.ContextThemeWrapper
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -36,11 +36,9 @@ abstract class StylishFragment : Fragment() {
|
||||
protected var themeId: Int = 0
|
||||
protected var contextThemed: Context? = null
|
||||
|
||||
override fun onAttach(context: Context?) {
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context != null) {
|
||||
this.themeId = Stylish.getThemeId(context)
|
||||
}
|
||||
this.themeId = Stylish.getThemeId(context)
|
||||
contextThemed = ContextThemeWrapper(context, themeId)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.Formatter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachment
|
||||
|
||||
class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<EntryAttachmentsAdapter.EntryBinariesViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList()
|
||||
var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
|
||||
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
|
||||
val entryAttachment = entryAttachmentsList[position]
|
||||
|
||||
holder.binaryFileTitle.text = entryAttachment.name
|
||||
holder.binaryFileSize.text = Formatter.formatFileSize(context,
|
||||
entryAttachment.binaryAttachment.length())
|
||||
holder.binaryFileCompression.apply {
|
||||
if (entryAttachment.binaryAttachment.isCompressed == true) {
|
||||
text = CompressionAlgorithm.GZip.getName(context.resources)
|
||||
visibility = View.VISIBLE
|
||||
} else {
|
||||
text = ""
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
holder.binaryFileProgress.apply {
|
||||
visibility = when (entryAttachment.downloadState) {
|
||||
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
|
||||
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
|
||||
}
|
||||
progress = entryAttachment.downloadProgression
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
onItemClickListener?.invoke(entryAttachment, position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return entryAttachmentsList.size
|
||||
}
|
||||
|
||||
fun updateProgress(entryAttachment: EntryAttachment) {
|
||||
val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name }
|
||||
if (indexEntryAttachment != -1) {
|
||||
entryAttachmentsList[indexEntryAttachment] = entryAttachment
|
||||
notifyItemChanged(indexEntryAttachment)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
entryAttachmentsList.clear()
|
||||
}
|
||||
|
||||
inner class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
|
||||
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
||||
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
|
||||
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
|
||||
class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
var entryHistoryList: MutableList<Entry> = ArrayList()
|
||||
var onItemClickListener: ((item: Entry, position: Int)->Unit)? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
|
||||
return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: EntryHistoryViewHolder, position: Int) {
|
||||
val entryHistory = entryHistoryList[position]
|
||||
|
||||
holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources)
|
||||
holder.titleView.text = entryHistory.title
|
||||
holder.usernameView.text = entryHistory.username
|
||||
holder.urlView.text = entryHistory.url
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
onItemClickListener?.invoke(entryHistory, position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return entryHistoryList.size
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
entryHistoryList.clear()
|
||||
}
|
||||
|
||||
inner class EntryHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var lastModifiedView: TextView = itemView.findViewById(R.id.entry_history_last_modified)
|
||||
var titleView: TextView = itemView.findViewById(R.id.entry_history_title)
|
||||
var usernameView: TextView = itemView.findViewById(R.id.entry_history_username)
|
||||
var urlView: TextView = itemView.findViewById(R.id.entry_history_url)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,26 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.adapter
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -15,10 +34,9 @@ import java.util.ArrayList
|
||||
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
var fields: MutableList<Field> = ArrayList()
|
||||
private var fields: MutableList<Field> = ArrayList()
|
||||
var onItemClickListener: OnItemClickListener? = null
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
|
||||
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
|
||||
return FieldViewHolder(view)
|
||||
@@ -34,6 +52,11 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
|
||||
return fields.size
|
||||
}
|
||||
|
||||
fun setFields(fieldsToAdd: List<Field>) {
|
||||
fields.clear()
|
||||
fields.addAll(fieldsToAdd)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
fields.clear()
|
||||
}
|
||||
@@ -1,43 +1,50 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.ViewSwitcher
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.fileselect.FileDatabaseModel
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryEntity
|
||||
import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class FileDatabaseHistoryAdapter(private val context: Context, private val listFiles: List<String>)
|
||||
class FileDatabaseHistoryAdapter(private val context: Context)
|
||||
: RecyclerView.Adapter<FileDatabaseHistoryAdapter.FileDatabaseHistoryViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private var fileItemOpenListener: FileItemOpenListener? = null
|
||||
private var fileSelectClearListener: FileSelectClearListener? = null
|
||||
private var fileInformationShowListener: FileInformationShowListener? = null
|
||||
private var fileItemOpenListener: ((FileDatabaseHistoryEntity)->Unit)? = null
|
||||
private var fileSelectClearListener: ((FileDatabaseHistoryEntity)->Boolean)? = null
|
||||
private var saveAliasListener: ((FileDatabaseHistoryEntity)->Unit)? = null
|
||||
|
||||
private val listDatabaseFiles = ArrayList<FileDatabaseHistoryEntity>()
|
||||
|
||||
private var mExpandedPosition = -1
|
||||
private var mPreviousExpandedPosition = -1
|
||||
|
||||
@ColorInt
|
||||
private val defaultColor: Int
|
||||
@@ -45,7 +52,6 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
|
||||
private val warningColor: Int
|
||||
|
||||
init {
|
||||
|
||||
val typedValue = TypedValue()
|
||||
val theme = context.theme
|
||||
theme.resolveAttribute(R.attr.colorAccent, typedValue, true)
|
||||
@@ -60,91 +66,120 @@ class FileDatabaseHistoryAdapter(private val context: Context, private val listF
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FileDatabaseHistoryViewHolder, position: Int) {
|
||||
val fileDatabaseModel = FileDatabaseModel(context, listFiles[position])
|
||||
// Context menu creation
|
||||
holder.fileContainer.setOnCreateContextMenuListener(ContextMenuBuilder(fileDatabaseModel))
|
||||
// Get info from position
|
||||
val fileHistoryEntity = listDatabaseFiles[position]
|
||||
val fileDatabaseInfo = FileDatabaseInfo(context, fileHistoryEntity.databaseUri)
|
||||
|
||||
// Click item to open file
|
||||
if (fileItemOpenListener != null)
|
||||
holder.fileContainer.setOnClickListener(FileItemClickListener(position))
|
||||
// Assign file name
|
||||
if (PreferencesUtil.isFullFilePathEnable(context))
|
||||
holder.fileName.text = Uri.decode(fileDatabaseModel.fileUri.toString())
|
||||
else
|
||||
holder.fileName.text = fileDatabaseModel.fileName
|
||||
holder.fileName.textSize = PreferencesUtil.getListTextSize(context)
|
||||
holder.fileContainer.setOnClickListener {
|
||||
fileItemOpenListener?.invoke(fileHistoryEntity)
|
||||
}
|
||||
|
||||
// File alias
|
||||
holder.fileAlias.text = fileDatabaseInfo.retrieveDatabaseAlias(fileHistoryEntity.databaseAlias)
|
||||
|
||||
// File path
|
||||
holder.filePath.text = UriUtil.decode(fileDatabaseInfo.fileUri?.toString())
|
||||
|
||||
holder.filePreciseInfoContainer.visibility = if (fileDatabaseInfo.found()) {
|
||||
// Modification
|
||||
holder.fileModification.text = fileDatabaseInfo.getModificationString()
|
||||
// Size
|
||||
holder.fileSize.text = fileDatabaseInfo.getSizeString()
|
||||
|
||||
View.VISIBLE
|
||||
} else
|
||||
View.GONE
|
||||
|
||||
// Click on information
|
||||
if (fileInformationShowListener != null)
|
||||
holder.fileInformation.setOnClickListener(FileInformationClickListener(fileDatabaseModel))
|
||||
val isExpanded = position == mExpandedPosition
|
||||
//This line hides or shows the layout in question
|
||||
holder.fileExpandContainer.visibility = if (isExpanded) View.VISIBLE else View.GONE
|
||||
|
||||
// Save alias modification
|
||||
holder.fileAliasCloseButton.setOnClickListener {
|
||||
// Change the alias
|
||||
fileHistoryEntity.databaseAlias = holder.fileAliasEdit.text.toString()
|
||||
saveAliasListener?.invoke(fileHistoryEntity)
|
||||
|
||||
// Finish save mode
|
||||
holder.fileMainSwitcher.showPrevious()
|
||||
// Refresh current position to show alias
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
// Open alias modification
|
||||
holder.fileModifyButton.setOnClickListener {
|
||||
holder.fileAliasEdit.setText(holder.fileAlias.text)
|
||||
holder.fileMainSwitcher.showNext()
|
||||
}
|
||||
|
||||
holder.fileDeleteButton.setOnClickListener {
|
||||
fileSelectClearListener?.invoke(fileHistoryEntity)
|
||||
}
|
||||
|
||||
if (isExpanded) {
|
||||
mPreviousExpandedPosition = position
|
||||
}
|
||||
|
||||
holder.fileInformation.setOnClickListener {
|
||||
mExpandedPosition = if (isExpanded) -1 else position
|
||||
|
||||
// Notify change
|
||||
if (mPreviousExpandedPosition < itemCount)
|
||||
notifyItemChanged(mPreviousExpandedPosition)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
// Refresh View / Close alias modification if not contains fileAlias
|
||||
if (holder.fileMainSwitcher.currentView.findViewById<View>(R.id.file_alias)
|
||||
!= holder.fileAlias)
|
||||
holder.fileMainSwitcher.showPrevious()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return listFiles.size
|
||||
return listDatabaseFiles.size
|
||||
}
|
||||
|
||||
fun setOnItemClickListener(fileItemOpenListener: FileItemOpenListener) {
|
||||
this.fileItemOpenListener = fileItemOpenListener
|
||||
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
|
||||
listDatabaseFiles.clear()
|
||||
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
|
||||
}
|
||||
|
||||
fun setFileSelectClearListener(fileSelectClearListener: FileSelectClearListener) {
|
||||
this.fileSelectClearListener = fileSelectClearListener
|
||||
fun deleteDatabaseFileHistory(fileDatabaseHistoryToDelete: FileDatabaseHistoryEntity) {
|
||||
listDatabaseFiles.remove(fileDatabaseHistoryToDelete)
|
||||
}
|
||||
|
||||
fun setFileInformationShowListener(fileInformationShowListener: FileInformationShowListener) {
|
||||
this.fileInformationShowListener = fileInformationShowListener
|
||||
fun setOnFileDatabaseHistoryOpenListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
|
||||
this.fileItemOpenListener = listener
|
||||
}
|
||||
|
||||
interface FileItemOpenListener {
|
||||
fun onFileItemOpenListener(itemPosition: Int)
|
||||
fun setOnFileDatabaseHistoryDeleteListener(listener : ((FileDatabaseHistoryEntity)->Boolean)?) {
|
||||
this.fileSelectClearListener = listener
|
||||
}
|
||||
|
||||
interface FileSelectClearListener {
|
||||
fun onFileSelectClearListener(fileDatabaseModel: FileDatabaseModel): Boolean
|
||||
}
|
||||
|
||||
interface FileInformationShowListener {
|
||||
fun onClickFileInformation(fileDatabaseModel: FileDatabaseModel)
|
||||
}
|
||||
|
||||
private inner class FileItemClickListener(private val position: Int) : View.OnClickListener {
|
||||
|
||||
override fun onClick(v: View) {
|
||||
fileItemOpenListener?.onFileItemOpenListener(position)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FileInformationClickListener(private val fileDatabaseModel: FileDatabaseModel) : View.OnClickListener {
|
||||
|
||||
override fun onClick(view: View) {
|
||||
fileInformationShowListener?.onClickFileInformation(fileDatabaseModel)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ContextMenuBuilder(private val fileDatabaseModel: FileDatabaseModel) : View.OnCreateContextMenuListener {
|
||||
|
||||
private val mOnMyActionClickListener = MenuItem.OnMenuItemClickListener { item ->
|
||||
if (fileSelectClearListener == null)
|
||||
return@OnMenuItemClickListener false
|
||||
when (item.itemId) {
|
||||
MENU_CLEAR -> fileSelectClearListener!!.onFileSelectClearListener(fileDatabaseModel)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(contextMenu: ContextMenu?, view: View?, contextMenuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
contextMenu?.add(Menu.NONE, MENU_CLEAR, Menu.NONE, R.string.remove_from_filelist)
|
||||
?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
||||
}
|
||||
fun setOnSaveAliasListener(listener : ((FileDatabaseHistoryEntity)->Unit)?) {
|
||||
this.saveAliasListener = listener
|
||||
}
|
||||
|
||||
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var fileContainer: View = itemView.findViewById(R.id.file_container)
|
||||
var fileName: TextView = itemView.findViewById(R.id.file_filename)
|
||||
var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info)
|
||||
|
||||
var fileAlias: TextView = itemView.findViewById(R.id.file_alias)
|
||||
var fileInformation: ImageView = itemView.findViewById(R.id.file_information)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var fileMainSwitcher: ViewSwitcher = itemView.findViewById(R.id.file_main_switcher)
|
||||
var fileAliasEdit: EditText = itemView.findViewById(R.id.file_alias_edit)
|
||||
var fileAliasCloseButton: ImageView = itemView.findViewById(R.id.file_alias_save)
|
||||
|
||||
private const val MENU_CLEAR = 1
|
||||
var fileExpandContainer: ViewGroup = itemView.findViewById(R.id.file_expand_container)
|
||||
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
|
||||
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
|
||||
var filePath: TextView = itemView.findViewById(R.id.file_path)
|
||||
var filePreciseInfoContainer: ViewGroup = itemView.findViewById(R.id.file_precise_info_container)
|
||||
var fileModification: TextView = itemView.findViewById(R.id.file_modification)
|
||||
var fileSize: TextView = itemView.findViewById(R.id.file_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,78 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.support.v7.util.SortedList
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.support.v7.widget.util.SortedListAdapterCallback
|
||||
import android.graphics.Paint
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SortedList
|
||||
import androidx.recyclerview.widget.SortedListAdapterCallback
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.App
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.strikeOut
|
||||
import java.util.*
|
||||
|
||||
class NodeAdapter
|
||||
/**
|
||||
* Create node list adapter with contextMenu or not
|
||||
* @param context Context to use
|
||||
*/
|
||||
(private val context: Context, private val menuInflater: MenuInflater)
|
||||
(private val context: Context)
|
||||
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
|
||||
|
||||
private val nodeSortedList: SortedList<NodeVersioned>
|
||||
private val nodeSortedList: SortedList<Node>
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private var textSize: Float = 0.toFloat()
|
||||
private var subtextSize: Float = 0.toFloat()
|
||||
private var iconSize: Float = 0.toFloat()
|
||||
private var listSort: SortNodeEnum? = null
|
||||
private var groupsBeforeSort: Boolean = false
|
||||
private var ascendingSort: Boolean = false
|
||||
private var showUserNames: Boolean = false
|
||||
|
||||
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
|
||||
private var textSizeUnit: Int = TypedValue.COMPLEX_UNIT_PX
|
||||
private var prefTextSize: Float = 0F
|
||||
private var subtextSize: Float = 0F
|
||||
private var infoTextSize: Float = 0F
|
||||
private var numberChildrenTextSize: Float = 0F
|
||||
private var iconSize: Float = 0F
|
||||
private var listSort: SortNodeEnum = SortNodeEnum.DB
|
||||
private var ascendingSort: Boolean = true
|
||||
private var groupsBeforeSort: Boolean = true
|
||||
private var recycleBinBottomSort: Boolean = true
|
||||
private var showUserNames: Boolean = true
|
||||
private var showNumberEntries: Boolean = true
|
||||
private var entryFilters = arrayOf<Group.ChildFilter>()
|
||||
|
||||
private var actionNodesList = LinkedList<Node>()
|
||||
private var nodeClickCallback: NodeClickCallback? = null
|
||||
private var nodeMenuListener: NodeMenuListener? = null
|
||||
private var activateContextMenu: Boolean = false
|
||||
private var readOnly: Boolean = false
|
||||
private var isASearchResult: Boolean = false
|
||||
|
||||
private val mDatabase: Database
|
||||
|
||||
@@ -74,20 +88,19 @@ class NodeAdapter
|
||||
|
||||
init {
|
||||
assignPreferences()
|
||||
this.activateContextMenu = false
|
||||
this.readOnly = false
|
||||
this.isASearchResult = false
|
||||
|
||||
this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback<NodeVersioned>(this) {
|
||||
override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int {
|
||||
return listSort?.getNodeComparator(ascendingSort, groupsBeforeSort)?.compare(item1, item2) ?: 0
|
||||
this.nodeSortedList = SortedList(Node::class.java, object : SortedListAdapterCallback<Node>(this) {
|
||||
override fun compare(item1: Node, item2: Node): Int {
|
||||
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean {
|
||||
return oldItem.title == newItem.title && oldItem.icon == newItem.icon
|
||||
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
|
||||
return oldItem.type == newItem.type
|
||||
&& oldItem.title == newItem.title
|
||||
&& oldItem.icon == newItem.icon
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(item1: NodeVersioned, item2: NodeVersioned): Boolean {
|
||||
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
|
||||
return item1 == item2
|
||||
}
|
||||
})
|
||||
@@ -105,75 +118,146 @@ class NodeAdapter
|
||||
taTextColor.recycle()
|
||||
}
|
||||
|
||||
fun setReadOnly(readOnly: Boolean) {
|
||||
this.readOnly = readOnly
|
||||
}
|
||||
|
||||
fun setIsASearchResult(isASearchResult: Boolean) {
|
||||
this.isASearchResult = isASearchResult
|
||||
}
|
||||
|
||||
fun setActivateContextMenu(activate: Boolean) {
|
||||
this.activateContextMenu = activate
|
||||
}
|
||||
|
||||
private fun assignPreferences() {
|
||||
val textSizeDefault = java.lang.Float.parseFloat(context.getString(R.string.list_size_default))
|
||||
this.textSize = PreferencesUtil.getListTextSize(context)
|
||||
this.subtextSize = context.resources.getInteger(R.integer.list_small_size_default) * textSize / textSizeDefault
|
||||
// Retrieve the icon size
|
||||
val iconDefaultSize = context.resources.getDimension(R.dimen.list_icon_size_default)
|
||||
this.iconSize = iconDefaultSize * textSize / textSizeDefault
|
||||
this.prefTextSize = PreferencesUtil.getListTextSize(context)
|
||||
this.infoTextSize = context.resources.getDimension(R.dimen.list_medium_size_default) * prefTextSize
|
||||
this.subtextSize = context.resources.getDimension(R.dimen.list_small_size_default) * prefTextSize
|
||||
this.numberChildrenTextSize = context.resources.getDimension(R.dimen.list_tiny_size_default) * prefTextSize
|
||||
this.iconSize = context.resources.getDimension(R.dimen.list_icon_size_default) * prefTextSize
|
||||
|
||||
this.listSort = PreferencesUtil.getListSort(context)
|
||||
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
|
||||
this.ascendingSort = PreferencesUtil.getAscendingSort(context)
|
||||
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
|
||||
this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context)
|
||||
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
|
||||
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
|
||||
|
||||
this.entryFilters = Group.ChildFilter.getDefaults(context)
|
||||
|
||||
// Reinit textSize for all view type
|
||||
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the list by clear and build children from the group
|
||||
*/
|
||||
fun rebuildList(group: GroupVersioned) {
|
||||
fun rebuildList(group: Group) {
|
||||
this.nodeSortedList.clear()
|
||||
assignPreferences()
|
||||
// TODO verify sort
|
||||
try {
|
||||
this.nodeSortedList.addAll(group.getChildrenWithoutMetaStream())
|
||||
this.nodeSortedList.addAll(group.getChildren(*entryFilters))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Can't add node elements to the list", e)
|
||||
Toast.makeText(context, "Can't add node elements to the list : " + e.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun contains(node: Node): Boolean {
|
||||
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the list
|
||||
* @param node Node to add
|
||||
*/
|
||||
fun addNode(node: NodeVersioned) {
|
||||
fun addNode(node: Node) {
|
||||
nodeSortedList.add(node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add nodes to the list
|
||||
* @param nodes Nodes to add
|
||||
*/
|
||||
fun addNodes(nodes: List<Node>) {
|
||||
nodeSortedList.addAll(nodes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node in the list
|
||||
* @param node Node to delete
|
||||
*/
|
||||
fun removeNode(node: NodeVersioned) {
|
||||
fun removeNode(node: Node) {
|
||||
nodeSortedList.remove(node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove nodes in the list
|
||||
* @param nodes Nodes to delete
|
||||
*/
|
||||
fun removeNodes(nodes: List<Node>) {
|
||||
nodes.forEach { node ->
|
||||
nodeSortedList.remove(node)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node at [position] in the list
|
||||
*/
|
||||
fun removeNodeAt(position: Int) {
|
||||
nodeSortedList.removeItemAt(position)
|
||||
// Refresh all the next items
|
||||
notifyItemRangeChanged(position, nodeSortedList.size() - position)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove nodes in the list by [positions]
|
||||
* Note : algorithm remove the higher position at each iteration
|
||||
*/
|
||||
fun removeNodesAt(positions: IntArray) {
|
||||
val positionsSortDescending = positions.toMutableList()
|
||||
positionsSortDescending.sortDescending()
|
||||
positionsSortDescending.forEach {
|
||||
removeNodeAt(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a node in the list
|
||||
* @param oldNode Node before the update
|
||||
* @param newNode Node after the update
|
||||
*/
|
||||
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) {
|
||||
fun updateNode(oldNode: Node, newNode: Node) {
|
||||
nodeSortedList.beginBatchedUpdates()
|
||||
nodeSortedList.remove(oldNode)
|
||||
nodeSortedList.add(newNode)
|
||||
nodeSortedList.endBatchedUpdates()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update nodes in the list
|
||||
* @param oldNodes Nodes before the update
|
||||
* @param newNodes Node after the update
|
||||
*/
|
||||
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||
nodeSortedList.beginBatchedUpdates()
|
||||
oldNodes.forEach { oldNode ->
|
||||
nodeSortedList.remove(oldNode)
|
||||
}
|
||||
nodeSortedList.addAll(newNodes)
|
||||
nodeSortedList.endBatchedUpdates()
|
||||
}
|
||||
|
||||
fun notifyNodeChanged(node: Node) {
|
||||
notifyItemChanged(nodeSortedList.indexOf(node))
|
||||
}
|
||||
|
||||
fun setActionNodes(actionNodes: List<Node>) {
|
||||
this.actionNodesList.apply {
|
||||
clear()
|
||||
addAll(actionNodes)
|
||||
}
|
||||
}
|
||||
|
||||
fun unselectActionNodes() {
|
||||
actionNodesList.forEach {
|
||||
notifyItemChanged(nodeSortedList.indexOf(it))
|
||||
}
|
||||
this.actionNodesList.apply {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a change sort of the list
|
||||
*/
|
||||
@@ -203,44 +287,73 @@ class NodeAdapter
|
||||
Type.GROUP -> iconGroupColor
|
||||
Type.ENTRY -> iconEntryColor
|
||||
}
|
||||
holder.icon.assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
|
||||
// Assign text
|
||||
holder.text.text = subNode.title
|
||||
// Assign click
|
||||
holder.container.setOnClickListener { nodeClickCallback?.onNodeClick(subNode) }
|
||||
// Context menu
|
||||
if (activateContextMenu) {
|
||||
holder.container.setOnCreateContextMenuListener(
|
||||
ContextMenuBuilder(menuInflater, subNode, readOnly, isASearchResult, nodeMenuListener))
|
||||
holder.icon.apply {
|
||||
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
|
||||
// Relative size of the icon
|
||||
layoutParams?.apply {
|
||||
height = iconSize.toInt()
|
||||
width = iconSize.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
// Add username
|
||||
holder.subText.text = ""
|
||||
holder.subText.visibility = View.GONE
|
||||
if (subNode.type == Type.ENTRY) {
|
||||
val entry = subNode as EntryVersioned
|
||||
// Assign text
|
||||
holder.text.apply {
|
||||
text = subNode.title
|
||||
setTextSize(textSizeUnit, infoTextSize)
|
||||
strikeOut(subNode.isCurrentlyExpires)
|
||||
}
|
||||
// Add subText with username
|
||||
holder.subText.apply {
|
||||
text = ""
|
||||
strikeOut(subNode.isCurrentlyExpires)
|
||||
visibility = View.GONE
|
||||
}
|
||||
|
||||
// Specific elements for entry
|
||||
if (subNode.type == Type.ENTRY) {
|
||||
val entry = subNode as Entry
|
||||
mDatabase.startManageEntry(entry)
|
||||
|
||||
holder.text.text = entry.getVisualTitle()
|
||||
|
||||
val username = entry.username
|
||||
if (showUserNames && username.isNotEmpty()) {
|
||||
holder.subText.visibility = View.VISIBLE
|
||||
holder.subText.text = username
|
||||
holder.subText.apply {
|
||||
val username = entry.username
|
||||
if (showUserNames && username.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
text = username
|
||||
setTextSize(textSizeUnit, subtextSize)
|
||||
}
|
||||
}
|
||||
|
||||
mDatabase.stopManageEntry(entry)
|
||||
}
|
||||
|
||||
// Assign image and text size
|
||||
// Relative size of the icon
|
||||
holder.icon.layoutParams?.height = iconSize.toInt()
|
||||
holder.icon.layoutParams?.width = iconSize.toInt()
|
||||
holder.text.textSize = textSize
|
||||
holder.subText.textSize = subtextSize
|
||||
// Add number of entries in groups
|
||||
if (subNode.type == Type.GROUP) {
|
||||
if (showNumberEntries) {
|
||||
holder.numberChildren?.apply {
|
||||
text = (subNode as Group)
|
||||
.getChildEntries(*entryFilters)
|
||||
.size.toString()
|
||||
setTextSize(textSizeUnit, numberChildrenTextSize)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
holder.numberChildren?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Assign click
|
||||
holder.container.setOnClickListener {
|
||||
nodeClickCallback?.onNodeClick(subNode)
|
||||
}
|
||||
holder.container.setOnLongClickListener {
|
||||
nodeClickCallback?.onNodeLongClick(subNode) ?: false
|
||||
}
|
||||
|
||||
holder.container.isSelected = actionNodesList.contains(subNode)
|
||||
}
|
||||
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return nodeSortedList.size()
|
||||
}
|
||||
@@ -252,103 +365,12 @@ class NodeAdapter
|
||||
this.nodeClickCallback = nodeClickCallback
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a listener when an element of menu is clicked
|
||||
*/
|
||||
fun setNodeMenuListener(nodeMenuListener: NodeMenuListener?) {
|
||||
this.nodeMenuListener = nodeMenuListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback listener to redefine to do an action when a node is click
|
||||
*/
|
||||
interface NodeClickCallback {
|
||||
fun onNodeClick(node: NodeVersioned)
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu listener to redefine to do an action in menu
|
||||
*/
|
||||
interface NodeMenuListener {
|
||||
fun onOpenMenuClick(node: NodeVersioned): Boolean
|
||||
fun onEditMenuClick(node: NodeVersioned): Boolean
|
||||
fun onCopyMenuClick(node: NodeVersioned): Boolean
|
||||
fun onMoveMenuClick(node: NodeVersioned): Boolean
|
||||
fun onDeleteMenuClick(node: NodeVersioned): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class for menu listener
|
||||
*/
|
||||
private class ContextMenuBuilder(val menuInflater: MenuInflater,
|
||||
val node: NodeVersioned,
|
||||
val readOnly: Boolean,
|
||||
val isASearchResult: Boolean,
|
||||
val menuListener: NodeMenuListener?)
|
||||
: View.OnCreateContextMenuListener {
|
||||
|
||||
private val mOnMyActionClickListener = MenuItem.OnMenuItemClickListener { item ->
|
||||
if (menuListener == null)
|
||||
return@OnMenuItemClickListener false
|
||||
when (item.itemId) {
|
||||
R.id.menu_open -> menuListener.onOpenMenuClick(node)
|
||||
R.id.menu_edit -> menuListener.onEditMenuClick(node)
|
||||
R.id.menu_copy -> menuListener.onCopyMenuClick(node)
|
||||
R.id.menu_move -> menuListener.onMoveMenuClick(node)
|
||||
R.id.menu_delete -> menuListener.onDeleteMenuClick(node)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(contextMenu: ContextMenu?,
|
||||
view: View?,
|
||||
contextMenuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
menuInflater.inflate(R.menu.node_menu, contextMenu)
|
||||
|
||||
// Opening
|
||||
var menuItem = contextMenu?.findItem(R.id.menu_open)
|
||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
||||
|
||||
val database = Database.getInstance()
|
||||
|
||||
// Edition
|
||||
if (readOnly || node == database.recycleBin) {
|
||||
contextMenu?.removeItem(R.id.menu_edit)
|
||||
} else {
|
||||
menuItem = contextMenu?.findItem(R.id.menu_edit)
|
||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
||||
}
|
||||
|
||||
// Copy (not for group)
|
||||
if (readOnly
|
||||
|| isASearchResult
|
||||
|| node == database.recycleBin
|
||||
|| node.type == Type.GROUP) {
|
||||
// TODO COPY For Group
|
||||
contextMenu?.removeItem(R.id.menu_copy)
|
||||
} else {
|
||||
menuItem = contextMenu?.findItem(R.id.menu_copy)
|
||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
||||
}
|
||||
|
||||
// Move
|
||||
if (readOnly
|
||||
|| isASearchResult
|
||||
|| node == database.recycleBin) {
|
||||
contextMenu?.removeItem(R.id.menu_move)
|
||||
} else {
|
||||
menuItem = contextMenu?.findItem(R.id.menu_move)
|
||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
||||
}
|
||||
|
||||
// Deletion
|
||||
if (readOnly || node == database.recycleBin) {
|
||||
contextMenu?.removeItem(R.id.menu_delete)
|
||||
} else {
|
||||
menuItem = contextMenu?.findItem(R.id.menu_delete)
|
||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
||||
}
|
||||
}
|
||||
fun onNodeClick(node: Node)
|
||||
fun onNodeLongClick(node: Node): Boolean
|
||||
}
|
||||
|
||||
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
@@ -356,6 +378,7 @@ class NodeAdapter
|
||||
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)
|
||||
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.adapters
|
||||
@@ -22,23 +22,21 @@ package com.kunzisoft.keepass.adapters
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.graphics.Color
|
||||
import android.support.v4.widget.CursorAdapter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.cursor.EntryCursor
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||
import com.kunzisoft.keepass.database.element.PwIcon
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import java.util.*
|
||||
import com.kunzisoft.keepass.view.strikeOut
|
||||
|
||||
class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
||||
: CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
|
||||
class SearchEntryCursorAdapter(private val context: Context,
|
||||
private val database: Database)
|
||||
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
|
||||
|
||||
private val cursorInflater: LayoutInflater = context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
@@ -72,34 +70,31 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
||||
|
||||
override fun bindView(view: View, context: Context, cursor: Cursor) {
|
||||
|
||||
// Retrieve elements from cursor
|
||||
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))
|
||||
val iconFactory = database.iconFactory
|
||||
var icon: PwIcon = iconFactory.getIcon(
|
||||
UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
|
||||
if (icon.isUnknown) {
|
||||
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)))
|
||||
if (icon.isUnknown)
|
||||
icon = iconFactory.keyIcon
|
||||
}
|
||||
val title = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE))
|
||||
val username = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME))
|
||||
val url = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL))
|
||||
database.getEntryFrom(cursor)?.let { currentEntry ->
|
||||
val viewHolder = view.tag as ViewHolder
|
||||
|
||||
val viewHolder = view.tag as ViewHolder
|
||||
// Assign image
|
||||
viewHolder.imageViewIcon?.assignDatabaseIcon(
|
||||
database.drawFactory,
|
||||
currentEntry.icon,
|
||||
iconColor)
|
||||
|
||||
// Assign image
|
||||
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
|
||||
// Assign title
|
||||
viewHolder.textViewTitle?.apply {
|
||||
text = currentEntry.getVisualTitle()
|
||||
strikeOut(currentEntry.isCurrentlyExpires)
|
||||
}
|
||||
|
||||
// Assign title
|
||||
val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString())
|
||||
viewHolder.textViewTitle?.text = showTitle
|
||||
if (displayUsername && username.isNotEmpty()) {
|
||||
viewHolder.textViewSubTitle?.text = String.format("(%s)", username)
|
||||
} else {
|
||||
viewHolder.textViewSubTitle?.text = ""
|
||||
// Assign subtitle
|
||||
viewHolder.textViewSubTitle?.apply {
|
||||
val entryUsername = currentEntry.username
|
||||
text = if (displayUsername && entryUsername.isNotEmpty()) {
|
||||
String.format("(%s)", entryUsername)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
strikeOut(currentEntry.isCurrentlyExpires)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,11 +105,11 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
||||
}
|
||||
|
||||
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
|
||||
return database.searchEntries(constraint.toString())
|
||||
return database.searchEntries(context, constraint.toString())
|
||||
}
|
||||
|
||||
fun getEntryFromPosition(position: Int): EntryVersioned? {
|
||||
var pwEntry: EntryVersioned? = null
|
||||
fun getEntryFromPosition(position: Int): Entry? {
|
||||
var pwEntry: Entry? = null
|
||||
|
||||
val cursor = this.cursor
|
||||
if (cursor.moveToFirst() && cursor.move(position)) {
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.app
|
||||
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.app.database
|
||||
|
||||
import android.os.AsyncTask
|
||||
|
||||
/**
|
||||
* Private class to invoke each method in a separate thread
|
||||
*/
|
||||
class ActionDatabaseAsyncTask<T>(
|
||||
private val action: () -> T ,
|
||||
private val afterActionDatabaseListener: ((T?) -> Unit)? = null
|
||||
) : AsyncTask<Void, Void, T>() {
|
||||
|
||||
override fun doInBackground(vararg args: Void?): T? {
|
||||
return action.invoke()
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: T?) {
|
||||
afterActionDatabaseListener?.invoke(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.app.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import android.content.Context
|
||||
|
||||
@Database(version = 1, entities = [FileDatabaseHistoryEntity::class, CipherDatabaseEntity::class])
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun fileDatabaseHistoryDao(): FileDatabaseHistoryDao
|
||||
abstract fun cipherDatabaseDao(): CipherDatabaseDao
|
||||
|
||||
companion object {
|
||||
fun getDatabase(applicationContext: Context): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "com.kunzisoft.keepass.database"
|
||||
).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user