mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
618 Commits
feature/Ca
...
2.5RC1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7182c2e66d | ||
|
|
503316bc70 | ||
|
|
d5af59f2c7 | ||
|
|
cb3ac1ad3a | ||
|
|
e161080e4c | ||
|
|
ceab7f917b | ||
|
|
41d8066c4c | ||
|
|
e373cbd776 | ||
|
|
05ea6b6b10 | ||
|
|
28eed3ae71 | ||
|
|
535c67ac9b | ||
|
|
68027a6e15 | ||
|
|
12dea6b499 | ||
|
|
d8bd078a02 | ||
|
|
439bc109b0 | ||
|
|
b1ec93ceb5 | ||
|
|
44b9aa0e48 | ||
|
|
0a59063027 | ||
|
|
5db4608abd | ||
|
|
6ece2aa6cb | ||
|
|
95ee45f666 | ||
|
|
556e90b8d8 | ||
|
|
42841e6247 | ||
|
|
324c82248a | ||
|
|
94f5a47918 | ||
|
|
3f70990956 | ||
|
|
d77635e572 | ||
|
|
0f26f1b751 | ||
|
|
456bc22138 | ||
|
|
b6fe91e396 | ||
|
|
0fb4d26949 | ||
|
|
3b3583a416 | ||
|
|
cac1d576c8 | ||
|
|
9cba0d0a48 | ||
|
|
3bf11e9dc0 | ||
|
|
edbb160ac6 | ||
|
|
ee111dc63c | ||
|
|
b06edb756a | ||
|
|
fd745494e0 | ||
|
|
702bf3f479 | ||
|
|
6adc02a91f | ||
|
|
55bae5a130 | ||
|
|
1aae817b17 | ||
|
|
8afc8c23fb | ||
|
|
36e473b139 | ||
|
|
e529723f86 | ||
|
|
6c5112c142 | ||
|
|
77ac68a603 | ||
|
|
e816f40872 | ||
|
|
65313f114b | ||
|
|
ef1f1342f5 | ||
|
|
9205fe6c08 | ||
|
|
75df3e81fe | ||
|
|
47fffbadb5 | ||
|
|
78354473fa | ||
|
|
8d7efb44b5 | ||
|
|
cab00b3d8c | ||
|
|
8efab01336 | ||
|
|
66feb8beb4 | ||
|
|
ca4cccffeb | ||
|
|
51b1760c50 | ||
|
|
42e1bda365 | ||
|
|
194b6b557a | ||
|
|
e7bc439997 | ||
|
|
ef76cce0ac | ||
|
|
63eec6d969 | ||
|
|
719ae74c06 | ||
|
|
37cf424eb8 | ||
|
|
3a87f7ba9d | ||
|
|
275428d825 | ||
|
|
7b9dac86ca | ||
|
|
46496ee2cc | ||
|
|
110cc402cc | ||
|
|
587f006259 | ||
|
|
ec45c0df81 | ||
|
|
dc575aeca4 | ||
|
|
a3f790f000 | ||
|
|
4e9e188d02 | ||
|
|
abb17efae4 | ||
|
|
16be990502 | ||
|
|
d1a496f9a3 | ||
|
|
cd730fcfef | ||
|
|
1b65bf665b | ||
|
|
6f4735790c | ||
|
|
040666f89d | ||
|
|
227cb800c3 | ||
|
|
0052569a14 | ||
|
|
f7561c4888 | ||
|
|
92200f19e7 | ||
|
|
4c01f18a62 | ||
|
|
a6ce3e49fe | ||
|
|
1b3a5d1bf6 | ||
|
|
db7bdc63c8 | ||
|
|
7aca550f02 | ||
|
|
b60980b3fd | ||
|
|
b578c2c584 | ||
|
|
fc45bd624e | ||
|
|
17be3d9d2c | ||
|
|
5b3a38a7bc | ||
|
|
4403835d50 | ||
|
|
b7a3d3eb46 | ||
|
|
29846b22fe | ||
|
|
32d235e8c7 | ||
|
|
cb982b3513 | ||
|
|
d7ed6c26dd | ||
|
|
8ff19f7e68 | ||
|
|
729e062c3a | ||
|
|
7d0340ac07 | ||
|
|
01960e74c1 | ||
|
|
8e40250985 | ||
|
|
8ae2edb61a | ||
|
|
0baa7bcbf1 | ||
|
|
fffee48918 | ||
|
|
515abb6e14 | ||
|
|
6c1c3ff87f | ||
|
|
5b65575c7a | ||
|
|
0f258fc5f8 | ||
|
|
206bc661dc | ||
|
|
d0e35b109e | ||
|
|
61769c4f20 | ||
|
|
95778ee5f4 | ||
|
|
155030fdca | ||
|
|
98237ef76c | ||
|
|
f0a7b38199 | ||
|
|
9fc5e6751b | ||
|
|
4c1630312b | ||
|
|
d397c5c996 | ||
|
|
f6c41b5a60 | ||
|
|
06eb5c01c3 | ||
|
|
7df49f91e8 | ||
|
|
96dcbb0ce7 | ||
|
|
5f828fb986 | ||
|
|
533d663938 | ||
|
|
ae788503a9 | ||
|
|
cf0acd9c73 | ||
|
|
0857f2f1cf | ||
|
|
c05d412bdb | ||
|
|
c8e0ce717d | ||
|
|
cc3485b201 | ||
|
|
81ea7080c2 | ||
|
|
76ff6f5ae0 | ||
|
|
0c0d0b7a6f | ||
|
|
ec8cf1f6b7 | ||
|
|
da44310d1b | ||
|
|
4bd9c84bb0 | ||
|
|
3b1269a770 | ||
|
|
7c986ccee8 | ||
|
|
903bad8f36 | ||
|
|
4b9577437c | ||
|
|
c316011fbc | ||
|
|
3fb1f18c22 | ||
|
|
53935058f5 | ||
|
|
a3860c9581 | ||
|
|
dc20899d26 | ||
|
|
62ac3ddb75 | ||
|
|
b792a61bf9 | ||
|
|
aae9f9e1cb | ||
|
|
d098bf5e6a | ||
|
|
b0e8a3ecd9 | ||
|
|
4efa684022 | ||
|
|
f2c8082990 | ||
|
|
1abba80045 | ||
|
|
68564a2b75 | ||
|
|
385b701b38 | ||
|
|
11c9a1d707 | ||
|
|
c5aef6b561 | ||
|
|
dfcf73cfd0 | ||
|
|
7fc9389700 | ||
|
|
d1af7349bc | ||
|
|
0fd955197d | ||
|
|
cbf33507d1 | ||
|
|
bc60a5d97e | ||
|
|
57596b2991 | ||
|
|
43b3602a52 | ||
|
|
c09ec961b8 | ||
|
|
d140b453b2 | ||
|
|
9eb42636ec | ||
|
|
ee2d663fce | ||
|
|
8e83615a22 | ||
|
|
0ff129c5ca | ||
|
|
e49858439f | ||
|
|
3df07f7f47 | ||
|
|
ff9e179593 | ||
|
|
7539fee04b | ||
|
|
71a339a58f | ||
|
|
9ef2ea016b | ||
|
|
de4936a16d | ||
|
|
574d2b8904 | ||
|
|
1f3f7634e7 | ||
|
|
3c0725baff | ||
|
|
b0e1411012 | ||
|
|
39daf4714d | ||
|
|
4706afa823 | ||
|
|
25977d389d | ||
|
|
f760110569 | ||
|
|
133e78fe97 | ||
|
|
d92e0c8620 | ||
|
|
62fdb69d6b | ||
|
|
def57c9fb2 | ||
|
|
21c9c898c3 | ||
|
|
1f03c922c2 | ||
|
|
3f6ae6bdac | ||
|
|
60615ee1eb | ||
|
|
92b0d1bfa9 | ||
|
|
237988dc1f | ||
|
|
a846ec29ca | ||
|
|
4533e96bff | ||
|
|
0a401c3ac9 | ||
|
|
468abaf077 | ||
|
|
4ccf2f641c | ||
|
|
34eb2785cf | ||
|
|
09dbfe323e | ||
|
|
1f06c5b425 | ||
|
|
b98e089f7a | ||
|
|
a0ad06ed0a | ||
|
|
ec63365429 | ||
|
|
2cb85e4346 | ||
|
|
0d7c479c51 | ||
|
|
5a6c21e662 | ||
|
|
d6cadac98f | ||
|
|
dac2fc2c37 | ||
|
|
0fb45cef0d | ||
|
|
5ebdbd4003 | ||
|
|
b30f1023cb | ||
|
|
e5f65a4d1e | ||
|
|
ab42a65aa4 | ||
|
|
e351456bfe | ||
|
|
452e68b08f | ||
|
|
d65beed7a1 | ||
|
|
f5a5a0e8cb | ||
|
|
98380a0906 | ||
|
|
22fe7508f3 | ||
|
|
c8e241fc76 | ||
|
|
2c943e00d0 | ||
|
|
7ddb83b72d | ||
|
|
50bac01699 | ||
|
|
e0a92dfadd | ||
|
|
b50c951091 | ||
|
|
2f589a95a9 | ||
|
|
53eac86a95 | ||
|
|
9412f8955e | ||
|
|
71c98d82b1 | ||
|
|
42c1a925b4 | ||
|
|
1e84534ffd | ||
|
|
b087733e37 | ||
|
|
675efe29ac | ||
|
|
9833af8225 | ||
|
|
790c624571 | ||
|
|
67a70a8453 | ||
|
|
64bb05e2dd | ||
|
|
c111db6e73 | ||
|
|
24c5915bd3 | ||
|
|
04c3717618 | ||
|
|
78275a0984 | ||
|
|
bd908ed10d | ||
|
|
5473deec95 | ||
|
|
653258afd2 | ||
|
|
00a2210eea | ||
|
|
ec5688a013 | ||
|
|
fc0c7b5708 | ||
|
|
464f7ac486 | ||
|
|
fb2457146c | ||
|
|
d90870838e | ||
|
|
8c45f23291 | ||
|
|
315a3efa52 | ||
|
|
28abb5ae6f | ||
|
|
bb78d89682 | ||
|
|
fd36e19168 | ||
|
|
58db516e44 | ||
|
|
2b875e94dc | ||
|
|
0ba2447f55 | ||
|
|
8e684d0d3a | ||
|
|
ce3c1d4685 | ||
|
|
2cd77d47eb | ||
|
|
ac9366e351 | ||
|
|
af356586f8 | ||
|
|
7650db81a4 | ||
|
|
8925c86afd | ||
|
|
8f1836009e | ||
|
|
5fcf3f9b95 | ||
|
|
1c466d9e40 | ||
|
|
b97c2d9cbc | ||
|
|
3d970e4967 | ||
|
|
765c8f53dd | ||
|
|
4e81caeadf | ||
|
|
08b10e4d58 | ||
|
|
f8594f72e8 | ||
|
|
24ebee07cd | ||
|
|
a06ebb0991 | ||
|
|
cd5d4498e7 | ||
|
|
0bd62780c6 | ||
|
|
896f9327d6 | ||
|
|
cc83a99efe | ||
|
|
b6da20fef7 | ||
|
|
2912678559 | ||
|
|
409e870ef0 | ||
|
|
3d84074a0a | ||
|
|
a717fdfed4 | ||
|
|
be1f68015b | ||
|
|
2c29dcf1f6 | ||
|
|
2b1173177f | ||
|
|
3c0f7dc79c | ||
|
|
39d813bf3a | ||
|
|
5e2bc0d05b | ||
|
|
ec751159ae | ||
|
|
dda8b95f83 | ||
|
|
286012fe2a | ||
|
|
ab27299789 | ||
|
|
dfcdd5aa88 | ||
|
|
a05ea52689 | ||
|
|
de12b5de5b | ||
|
|
57b03eaca4 | ||
|
|
36bd00b760 | ||
|
|
4578a9974a | ||
|
|
d0371f58c6 | ||
|
|
9f80457351 | ||
|
|
56daf6f676 | ||
|
|
7f7b8d423b | ||
|
|
031afc80cb | ||
|
|
b77e28b04d | ||
|
|
2f8c3fdcfe | ||
|
|
f501a87099 | ||
|
|
d0ec5f26dd | ||
|
|
97776e9329 | ||
|
|
a19356c49e | ||
|
|
4f16918cf0 | ||
|
|
1af9761144 | ||
|
|
d74e814c79 | ||
|
|
16d09bca6c | ||
|
|
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 | ||
|
|
21c6ea73b2 | ||
|
|
51dc302bb0 | ||
|
|
fb023b81b5 | ||
|
|
9fd342f1e7 | ||
|
|
8988f17765 | ||
|
|
b7dc13d863 | ||
|
|
6e00fa2d01 | ||
|
|
2573434763 | ||
|
|
f153c26fef | ||
|
|
125f461cbe | ||
|
|
b705b4b712 | ||
|
|
c67b0bb858 | ||
|
|
c4d3c8cbfb | ||
|
|
4e16ba5f56 | ||
|
|
7137a2fadb | ||
|
|
9d90d0eaba | ||
|
|
0aa0b3e993 | ||
|
|
f6985c8944 | ||
|
|
4388d56c52 | ||
|
|
a70fe24c97 | ||
|
|
8e0392753c | ||
|
|
b0ec4942bc | ||
|
|
2cbc9675f6 | ||
|
|
94bdb0e3da | ||
|
|
65360c2a1e | ||
|
|
70d30bdbe6 | ||
|
|
66f7e6d1b1 | ||
|
|
5e5baa4892 | ||
|
|
9d1257ed9d | ||
|
|
9d7546053d | ||
|
|
9558fcaf21 |
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -20,9 +20,6 @@ Steps to reproduce the behavior:
|
||||
**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]
|
||||
@@ -30,7 +27,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Size: [e.g. 150Mo]
|
||||
- Contains attachment: [e.g. Yes]
|
||||
|
||||
**KeePass DX (please complete the following information):**
|
||||
**KeePassDX (please complete the following information):**
|
||||
- Version: [e.g. 2.5.0.0beta23]
|
||||
- Build: [e.g. Free]
|
||||
- Language: [e.g. French]
|
||||
@@ -38,7 +35,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
**Android (please complete the following information):**
|
||||
- Device: [e.g. GalaxyS8]
|
||||
- Version: [e.g. 8.1]
|
||||
- Browser: [e.g. Chrome]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
- Browser for Autofill: [e.g. Chrome version X]
|
||||
|
||||
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
|
||||
|
||||
120
CHANGELOG
120
CHANGELOG
@@ -1,14 +1,72 @@
|
||||
KeepassDX (2.5.0.0beta24)
|
||||
KeePassDX(2.5beta31)
|
||||
* Add write permission to keep compatibility with old file managers
|
||||
* Fix autofill for apps
|
||||
* Auto search for autofill
|
||||
* New keyfile input
|
||||
* Icon to hide keyfile input
|
||||
* New lock button
|
||||
* Setting to hide lock button in user interface
|
||||
* Clickable links in notes
|
||||
* Fix autofill for key-value pairs
|
||||
|
||||
KeePassDX(2.5beta30)
|
||||
* Fix Lock after screen off (wait 1.5 seconds)
|
||||
* Upgrade autofill algorithm
|
||||
* Fix ANR during file verifications
|
||||
|
||||
KeePassDX(2.5beta29)
|
||||
* Upgrade autofill algorithm
|
||||
* Delete registered KeyFile after save new credentials
|
||||
* Fix title and username entry view refresh after an update
|
||||
* Fix database lock request (open notification always active)
|
||||
* Allow empty title in entries
|
||||
* Add expiration datetime
|
||||
|
||||
KeePassDX(2.5beta28)
|
||||
* Fix read only database
|
||||
* Upgrade to Android SDK 29
|
||||
|
||||
KeePassDX (2.5beta27)
|
||||
* New setting to hide broken links
|
||||
* Show URL when title is empty
|
||||
* Setting to open search field at database opening
|
||||
* Fix settings for database locations
|
||||
* Fix error message when database file not writable
|
||||
* Fix appearance refresh settings
|
||||
* Sort optimization
|
||||
|
||||
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)
|
||||
* Fix settings
|
||||
* Fix edit group
|
||||
* Fix small bugs
|
||||
* Open/Save database as service / Add persistent notification
|
||||
* Fix settings / edit group / small bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta23)
|
||||
KeePassDX (2.5.0.0beta23)
|
||||
* New, more secure database creation workflow
|
||||
* Recognize more database files
|
||||
* Add alias for history files (WARNING: history is erased)
|
||||
@@ -17,14 +75,14 @@ KeepassDX (2.5.0.0beta23)
|
||||
* Fix OOM with KeyFile
|
||||
* Fix small issues
|
||||
|
||||
KeepassDX (2.5.0.0beta22)
|
||||
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)
|
||||
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
|
||||
@@ -32,10 +90,10 @@ KeepassDX (2.5.0.0beta21)
|
||||
* Fix button disabled with only KeyFile
|
||||
* Show the number of entries in a group
|
||||
|
||||
KeepassDX (2.5.0.0beta20)
|
||||
KeePassDX (2.5.0.0beta20)
|
||||
* Fix a major bug that displays an entry history
|
||||
|
||||
KeepassDX (2.5.0.0beta19)
|
||||
KeePassDX (2.5.0.0beta19)
|
||||
* Add lock button always visible
|
||||
* New connection workflow
|
||||
* Code refactored in Kotlin
|
||||
@@ -46,7 +104,7 @@ KeepassDX (2.5.0.0beta19)
|
||||
* Fix memory when load database
|
||||
* Fix small bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta18)
|
||||
KeePassDX (2.5.0.0beta18)
|
||||
* New recent databases views
|
||||
* New information dialog
|
||||
* Custom fields for the Magikeyboard
|
||||
@@ -55,10 +113,10 @@ KeepassDX (2.5.0.0beta18)
|
||||
* Fix memory when opening the database
|
||||
* Memory management for attachments
|
||||
|
||||
KeepassDX (2.5.0.0beta17)
|
||||
KeePassDX (2.5.0.0beta17)
|
||||
* Fix font and search
|
||||
|
||||
KeepassDX (2.5.0.0beta16)
|
||||
KeePassDX (2.5.0.0beta16)
|
||||
* New search in a single fragment
|
||||
* Search suggestions
|
||||
* Added the display of usernames
|
||||
@@ -66,20 +124,20 @@ KeepassDX (2.5.0.0beta16)
|
||||
* Fix read-only mode
|
||||
* Fix parcelable / toolbar / back
|
||||
|
||||
KeepassDX (2.5.0.0beta15)
|
||||
KeePassDX (2.5.0.0beta15)
|
||||
* Read only mode
|
||||
* Best group recovery for the navigation fragment
|
||||
* Fix copies in notifications
|
||||
* Fix orientation
|
||||
* Added translations
|
||||
|
||||
KeepassDX (2.5.0.0beta14)
|
||||
KeePassDX (2.5.0.0beta14)
|
||||
* Optimize all the memory with parcelables / fix search
|
||||
|
||||
KeepassDX (2.5.0.0beta13)
|
||||
KeePassDX (2.5.0.0beta13)
|
||||
* Fix memory issue with parcelable (crash in beta12 version)
|
||||
|
||||
KeepassDX (2.5.0.0beta12)
|
||||
KeePassDX (2.5.0.0beta12)
|
||||
* Added the Magikeyboard to fill the forms (settings still in development)
|
||||
* Added move and copy for groups and entries
|
||||
* New navigation in a single screen / new animations between activities
|
||||
@@ -92,10 +150,10 @@ KeepassDX (2.5.0.0beta12)
|
||||
* Fix the fingerprint recognition (WARNING : The keystore is reinit, you must delete the old keys)
|
||||
* Fix small bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta11)
|
||||
KeePassDX (2.5.0.0beta11)
|
||||
* Fix crash in beta10 version
|
||||
|
||||
KeepassDX (2.5.0.0beta10)
|
||||
KeePassDX (2.5.0.0beta10)
|
||||
* Dynamically change Algorithm and Key Derivation Function in settings
|
||||
* Upgrade translations
|
||||
* New red volcano theme, fix classic dark theme
|
||||
@@ -103,7 +161,7 @@ KeepassDX (2.5.0.0beta10)
|
||||
* Update fingerprint state with checkbox
|
||||
* Fix bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta9)
|
||||
KeePassDX (2.5.0.0beta9)
|
||||
* Education Screens to learn how to use the app
|
||||
* New designs
|
||||
* New custom font for character visibility
|
||||
@@ -112,9 +170,9 @@ KeepassDX (2.5.0.0beta9)
|
||||
* Change setting organisation
|
||||
* Pro version
|
||||
|
||||
KeepassDX (2.5.0.0beta8)
|
||||
KeePassDX (2.5.0.0beta8)
|
||||
* Hide custom entries protected
|
||||
* Best management of field references (https://keepass.info/help/base/fieldrefs.html)
|
||||
* Best management of field references (https://KeePass.info/help/base/fieldrefs.html)
|
||||
* Change database / default settings
|
||||
* Add Autofill for search
|
||||
* Add sorting by last access and by creation time
|
||||
@@ -122,7 +180,7 @@ KeepassDX (2.5.0.0beta8)
|
||||
* Refactor old code
|
||||
* Fix bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta7)
|
||||
KeePassDX (2.5.0.0beta7)
|
||||
* Rebuild Notifications
|
||||
* Change links to https
|
||||
* Add extended Ascii (ñæËÌÂÝÜ...)
|
||||
@@ -131,10 +189,10 @@ KeepassDX (2.5.0.0beta7)
|
||||
* Add setting to prevent the password copy
|
||||
* Fix bugs
|
||||
|
||||
KeepassDX (2.5.0.0beta6)
|
||||
KeePassDX (2.5.0.0beta6)
|
||||
* Fix crash
|
||||
|
||||
KeepassDX (2.5.0.0beta5)
|
||||
KeePassDX (2.5.0.0beta5)
|
||||
* Autofill (Android O)
|
||||
* Deletion for group
|
||||
* New sorts with (Asc/Dsc, Groups before or after)
|
||||
@@ -155,7 +213,7 @@ KeepassDX (2.5.0.0beta5)
|
||||
* Fix many small bugs
|
||||
* Add recycle bin setting (not yet accessible)
|
||||
|
||||
KeepassDX (2.5.0.0beta4)
|
||||
KeePassDX (2.5.0.0beta4)
|
||||
* Show only file name
|
||||
* Setting for full path
|
||||
* Add information for each database file
|
||||
@@ -164,7 +222,7 @@ KeepassDX (2.5.0.0beta4)
|
||||
* Delete view assignment for fingerprint opening
|
||||
* Merge KeePassDroid 2.2.1
|
||||
|
||||
KeepassDX (2.5.0.0beta3)
|
||||
KeePassDX (2.5.0.0beta3)
|
||||
* New database workflow with new screens and folder selection
|
||||
* Settings for default password generation
|
||||
* Fingerprint dialog for explanations
|
||||
@@ -175,17 +233,17 @@ KeepassDX (2.5.0.0beta3)
|
||||
* Merge KeePassDroid 2.2.0.9
|
||||
* Add corruption fix mode
|
||||
|
||||
KeepassDX (2.5.0.0beta2)
|
||||
KeePassDX (2.5.0.0beta2)
|
||||
* Remove libs for F-Droid
|
||||
|
||||
KeepassDX (2.5.0.0beta1)
|
||||
* Fork KeepassDroid
|
||||
KeePassDX (2.5.0.0beta1)
|
||||
* Fork KeePassDroid
|
||||
* Add Material Design
|
||||
* Add Light and Night theme
|
||||
* Min API is 14
|
||||
* Solve bug for fingerprint
|
||||
* Update French translation
|
||||
* Change donation (see KeepassDroid to contribute on both projects)
|
||||
* Change donation (see KeePassDroid to contribute on both projects)
|
||||
|
||||
KeePassDroid (2.2.1)
|
||||
* Fix kdbx4 date corruption
|
||||
@@ -446,7 +504,7 @@ KeePassDroid (1.9.10)
|
||||
|
||||
KeePassDroid (1.9.9)
|
||||
* Go back to explicitly storing blank fields in the database
|
||||
(works around bug in keepassx)
|
||||
(works around bug in KeePassx)
|
||||
* Add support for native code on MIPS architectures
|
||||
* Adding Vibrate permission. On some devices notifications fail
|
||||
without the vibrate permission.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
41
ReadMe.md
41
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,28 +8,29 @@
|
||||
|
||||
* 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.*
|
||||
*Note : If you access the application from a store, visual styles may not be available to encourage contribution to the work of open source projects. These optional styles are accessible after a contribution (and a congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
|
||||
|
||||
## Contributions
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -52,33 +53,35 @@ You can contribute in different ways to help us on our work.
|
||||
alt="Get it on Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
|
||||
|
||||
## F.A.Q.
|
||||
## Frequently Asked Questions
|
||||
|
||||
Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
|
||||
Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
|
||||
|
||||
## Other devices
|
||||
|
||||
- [KeePass XC](https://keepassxc.org/) (https://keepassxc.org/) works with **GNU/Linux**, **Mac** and **Windows**, is updated regularly and under the terms of the GNU General Public License. This is the recommended version for computers.
|
||||
- [KeePass](https://keepass.info/) (https://keepass.info/) is the original and official project for desktop, with technical documentation for standardized database files. It is updated regularly with active maintenance (written in C#).
|
||||
|
||||
- [KeePass](https://keepass.info/) (https://keepass.info/) is the historical project, with good technical documentation for standardized database files but only running on **Windows**.
|
||||
- [KeePassXC](https://keepassxc.org/) (https://keepassxc.org/) is an alternative integration to KeePass written in C++.
|
||||
|
||||
- [KeeWeb](https://keeweb.info/) (https://keeweb.info/) is a web version also compatible with KeePass files.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 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
|
||||
@@ -4,22 +4,28 @@ apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
ndkVersion "20.0.5594570"
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
versionCode = 24
|
||||
versionName = "2.5.0.0beta24"
|
||||
targetSdkVersion 29
|
||||
versionCode = 31
|
||||
versionName = "2.5RC1"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
||||
|
||||
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg("room.incremental", "true")
|
||||
arg("room.schemaLocation", "$projectDir/schemas".toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
@@ -28,7 +34,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled = false
|
||||
@@ -81,7 +86,7 @@ android {
|
||||
}
|
||||
|
||||
def spongycastleVersion = "1.58.0.0"
|
||||
def room_version = "2.2.0"
|
||||
def room_version = "2.2.5"
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
@@ -90,7 +95,10 @@ dependencies {
|
||||
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-rc01'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.biometric:biometric:1.0.1'
|
||||
implementation "androidx.core:core-ktx:1.2.0"
|
||||
// To upgrade with style
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
@@ -107,8 +115,8 @@ dependencies {
|
||||
// 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'
|
||||
// Apache Commons Codec
|
||||
implementation 'commons-codec:commons-codec:1.11'
|
||||
// Icon pack
|
||||
implementation project(path: ':icon-pack-classic')
|
||||
implementation project(path: ':icon-pack-material')
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "56438e5f7372ef3e36e33b782aed245d",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "file_database_history",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `database_alias` TEXT NOT NULL, `keyfile_uri` TEXT, `updated` INTEGER NOT NULL, PRIMARY KEY(`database_uri`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "databaseUri",
|
||||
"columnName": "database_uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "databaseAlias",
|
||||
"columnName": "database_alias",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyFileUri",
|
||||
"columnName": "keyfile_uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "updated",
|
||||
"columnName": "updated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"database_uri"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "cipher_database",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `encrypted_value` TEXT NOT NULL, `specs_parameters` TEXT NOT NULL, PRIMARY KEY(`database_uri`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "databaseUri",
|
||||
"columnName": "database_uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "encryptedValue",
|
||||
"columnName": "encrypted_value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "specParameters",
|
||||
"columnName": "specs_parameters",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"database_uri"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '56438e5f7372ef3e36e33b782aed245d')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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,38 +1,33 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
* 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.tests
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.Calendar
|
||||
import java.util.Random
|
||||
|
||||
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.*
|
||||
|
||||
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
|
||||
|
||||
class TypesTest : TestCase() {
|
||||
class StringDatabaseKDBUtilsTest : TestCase() {
|
||||
|
||||
fun testReadWriteLongZero() {
|
||||
testReadWriteLong(0.toByte())
|
||||
@@ -56,15 +51,9 @@ class TypesTest : TestCase() {
|
||||
|
||||
private fun testReadWriteLong(value: Byte) {
|
||||
val orig = ByteArray(8)
|
||||
val dest = ByteArray(8)
|
||||
|
||||
setArray(orig, value, 0, 8)
|
||||
|
||||
val one = LEDataInputStream.readLong(orig, 0)
|
||||
LEDataOutputStream.writeLong(one, dest, 0)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
setArray(orig, value, 8)
|
||||
|
||||
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
|
||||
}
|
||||
|
||||
fun testReadWriteIntZero() {
|
||||
@@ -81,24 +70,22 @@ class TypesTest : TestCase() {
|
||||
|
||||
private fun testReadWriteInt(value: Byte) {
|
||||
val orig = ByteArray(4)
|
||||
val dest = ByteArray(4)
|
||||
|
||||
for (i in 0..3) {
|
||||
orig[i] = 0
|
||||
}
|
||||
|
||||
setArray(orig, value, 0, 4)
|
||||
setArray(orig, value, 4)
|
||||
|
||||
val one = LEDataInputStream.readInt(orig, 0)
|
||||
|
||||
LEDataOutputStream.writeInt(one, dest, 0)
|
||||
val one = bytes4ToInt(orig)
|
||||
val dest = intTo4Bytes(one)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
|
||||
}
|
||||
|
||||
private fun setArray(buf: ByteArray, value: Byte, offset: Int, size: Int) {
|
||||
for (i in offset until offset + size) {
|
||||
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
||||
for (i in 0 until size) {
|
||||
buf[i] = value
|
||||
}
|
||||
}
|
||||
@@ -109,11 +96,10 @@ class TypesTest : TestCase() {
|
||||
orig[0] = 0
|
||||
orig[1] = 1
|
||||
|
||||
val one = LEDataInputStream.readUShort(orig, 0)
|
||||
val dest = LEDataOutputStream.writeUShortBuf(one)
|
||||
val one = bytes2ToUShort(orig)
|
||||
val dest = uShortTo2Bytes(one)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
|
||||
}
|
||||
|
||||
fun testReadWriteShortMin() {
|
||||
@@ -126,15 +112,12 @@ class TypesTest : TestCase() {
|
||||
|
||||
private fun testReadWriteShort(value: Byte) {
|
||||
val orig = ByteArray(2)
|
||||
val dest = ByteArray(2)
|
||||
setArray(orig, value, 2)
|
||||
|
||||
setArray(orig, value, 0, 2)
|
||||
|
||||
val one = LEDataInputStream.readUShort(orig, 0)
|
||||
LEDataOutputStream.writeUShort(one, dest, 0)
|
||||
val one = bytes2ToUShort(orig)
|
||||
val dest = uShortTo2Bytes(one)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
|
||||
}
|
||||
|
||||
fun testReadWriteByteZero() {
|
||||
@@ -150,16 +133,8 @@ class TypesTest : TestCase() {
|
||||
}
|
||||
|
||||
private fun testReadWriteByte(value: Byte) {
|
||||
val orig = ByteArray(1)
|
||||
val dest = ByteArray(1)
|
||||
|
||||
setArray(orig, value, 0, 1)
|
||||
|
||||
val one = Types.readUByte(orig, 0)
|
||||
Types.writeUByte(one, dest, 0)
|
||||
|
||||
assertArrayEquals(orig, dest)
|
||||
|
||||
val dest: Byte = uIntToByte(byteToUInt(value))
|
||||
assert(value == dest)
|
||||
}
|
||||
|
||||
fun testDate() {
|
||||
@@ -168,27 +143,37 @@ class TypesTest : TestCase() {
|
||||
val expected = Calendar.getInstance()
|
||||
expected.set(2008, 1, 2, 3, 4, 5)
|
||||
|
||||
val buf = PwDate.writeTime(expected.time, cal)
|
||||
val actual = Calendar.getInstance()
|
||||
actual.time = PwDate.readTime(buf, 0, cal)
|
||||
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: ", 1, actual.get(Calendar.DAY_OF_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 rnd = Random()
|
||||
val bUUID = ByteArray(16)
|
||||
rnd.nextBytes(bUUID)
|
||||
Random().nextBytes(bUUID)
|
||||
|
||||
val uuid = Types.bytestoUUID(bUUID)
|
||||
val eUUID = Types.UUIDtoBytes(uuid)
|
||||
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)
|
||||
@@ -199,8 +184,8 @@ class TypesTest : TestCase() {
|
||||
}
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
val leos = LEDataOutputStream(bos)
|
||||
leos.writeLong(Types.ULONG_MAX_VALUE)
|
||||
val leos = LittleEndianDataOutputStream(bos)
|
||||
leos.writeLong(ULONG_MAX_VALUE)
|
||||
leos.close()
|
||||
|
||||
val uLongMax = bos.toByteArray()
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.tests.crypto
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.tests.crypto
|
||||
@@ -39,9 +39,8 @@ 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
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
|
||||
|
||||
class CipherTest : TestCase() {
|
||||
private val rand = Random()
|
||||
@@ -93,7 +92,7 @@ class CipherTest : TestCase() {
|
||||
|
||||
val bis = ByteArrayInputStream(secrettext)
|
||||
val cis = BetterCipherInputStream(bis, decrypt)
|
||||
val lis = LEDataInputStream(cis)
|
||||
val lis = LittleEndianDataInputStream(cis)
|
||||
|
||||
val decrypttext = lis.readBytes(MESSAGE_LENGTH)
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.tests.crypto
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.tests.stream
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.tests.utils
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.kunzisoft.keepass"
|
||||
android:installLocation="auto">
|
||||
<supports-screens
|
||||
@@ -7,10 +8,14 @@
|
||||
android:normalScreens="true"
|
||||
android:largeScreens="true"
|
||||
android:anyDensity="true" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<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 +25,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"
|
||||
@@ -124,28 +132,35 @@
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.AboutActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/menu_about" />
|
||||
android:label="@string/about" />
|
||||
<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.settings.AutofillSettingsActivity" />
|
||||
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
||||
android:label="@string/keyboard_name"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
|
||||
<activity android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
|
||||
android:label="@string/keyboard_setting_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
</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"
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
/*
|
||||
* 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 androidx.appcompat.widget.Toolbar
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.widget.TextView
|
||||
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.text.HtmlCompat
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
class AboutActivity : StylishActivity() {
|
||||
@@ -40,7 +40,7 @@ class AboutActivity : StylishActivity() {
|
||||
setContentView(R.layout.activity_about)
|
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
toolbar.title = getString(R.string.menu_about)
|
||||
toolbar.title = getString(R.string.about)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
@@ -64,9 +64,17 @@ class AboutActivity : StylishActivity() {
|
||||
val buildTextView = findViewById<TextView>(R.id.activity_about_build)
|
||||
buildTextView.text = build
|
||||
|
||||
findViewById<TextView>(R.id.activity_about_licence_text).apply {
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
text = HtmlCompat.fromHtml(getString(R.string.html_about_licence, DateTime().year),
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
}
|
||||
|
||||
val disclaimerText = findViewById<TextView>(R.id.disclaimer)
|
||||
disclaimerText.text = getString(R.string.disclaimer_formal, DateTime().year)
|
||||
findViewById<TextView>(R.id.activity_about_contribution_text).apply {
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution),
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
|
||||
@@ -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,51 +22,72 @@ import android.app.Activity
|
||||
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 com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.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 androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
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.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.UriUtil
|
||||
import com.kunzisoft.keepass.utils.createDocument
|
||||
import com.kunzisoft.keepass.utils.onCreateDocumentResult
|
||||
import com.kunzisoft.keepass.view.EntryContentsView
|
||||
import com.kunzisoft.keepass.view.showActionError
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class EntryActivity : LockingHideActivity() {
|
||||
class EntryActivity : LockingActivity() {
|
||||
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
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 lockView: View? = null
|
||||
private var toolbar: Toolbar? = null
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
private var mEntry: EntryVersioned? = 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
|
||||
|
||||
@@ -96,29 +117,62 @@ class EntryActivity : LockingHideActivity() {
|
||||
invalidateOptionsMenu()
|
||||
|
||||
// Get views
|
||||
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
|
||||
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)
|
||||
lockView = findViewById(R.id.lock_button)
|
||||
|
||||
lockView?.setOnClickListener {
|
||||
lockAndExit()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
coordinatorLayout?.showActionError(result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Show the lock button
|
||||
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
// Get Entry from UUID
|
||||
try {
|
||||
val keyEntry: PwNodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
|
||||
val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY)
|
||||
if (keyEntry != null) {
|
||||
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, -1)
|
||||
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
|
||||
mEntryHistoryPosition = historyPosition
|
||||
if (historyPosition >= 0) {
|
||||
mIsHistory = true
|
||||
mEntry = mEntry?.getHistory()?.get(historyPosition)
|
||||
@@ -152,10 +206,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)
|
||||
@@ -188,7 +257,7 @@ class EntryActivity : LockingHideActivity() {
|
||||
"\n\n" +
|
||||
getString(R.string.clipboard_warning))
|
||||
.create().apply {
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) {dialog, _ ->
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
||||
dialog.dismiss()
|
||||
fillEntryDataInContentsView(entry)
|
||||
@@ -220,6 +289,17 @@ class EntryActivity : LockingHideActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
//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?.assignComment(entry.notes)
|
||||
|
||||
@@ -251,6 +331,27 @@ class EntryActivity : LockingHideActivity() {
|
||||
}
|
||||
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
|
||||
entryContentsView?.assignCreationDate(entry.creationTime)
|
||||
entryContentsView?.assignModificationDate(entry.lastModificationTime)
|
||||
@@ -262,9 +363,6 @@ class EntryActivity : LockingHideActivity() {
|
||||
entryContentsView?.assignExpiresDate(getString(R.string.never))
|
||||
}
|
||||
|
||||
// Assign special data
|
||||
entryContentsView?.assignUUID(entry.nodeId.id)
|
||||
|
||||
// Manage history
|
||||
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
|
||||
if (mIsHistory) {
|
||||
@@ -273,21 +371,26 @@ class EntryActivity : LockingHideActivity() {
|
||||
taColorAccent.recycle()
|
||||
}
|
||||
val entryHistory = entry.getHistory()
|
||||
// isMainEntry = not an history
|
||||
// TODO isMainEntry = not an history
|
||||
val showHistoryView = entryHistory.isNotEmpty()
|
||||
entryContentsView?.showHistory(showHistoryView)
|
||||
if (showHistoryView) {
|
||||
entryContentsView?.assignHistory(entryHistory)
|
||||
entryContentsView?.onHistoryClick { historyItem, position ->
|
||||
launch(this, historyItem, true, 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
|
||||
@@ -295,6 +398,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?) {
|
||||
@@ -313,9 +425,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 (mReadOnly) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -359,8 +474,7 @@ class EntryActivity : LockingHideActivity() {
|
||||
getString(R.string.entry_user_name)))
|
||||
},
|
||||
{
|
||||
// Launch autofill settings
|
||||
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
|
||||
performedNextEducation(entryActivityEducation, menu)
|
||||
})
|
||||
|
||||
if (!entryCopyEducationPerformed) {
|
||||
@@ -384,21 +498,18 @@ class EntryActivity : LockingHideActivity() {
|
||||
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 ?: ""
|
||||
|
||||
@@ -408,30 +519,39 @@ class EntryActivity : LockingHideActivity() {
|
||||
}
|
||||
|
||||
UriUtil.gotoUrl(this, url)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_lock -> {
|
||||
lockAndExit()
|
||||
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_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)
|
||||
}
|
||||
|
||||
|
||||
override fun finish() {
|
||||
// Transit data in previous Activity after an update
|
||||
/*
|
||||
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);
|
||||
*/
|
||||
Intent().apply {
|
||||
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry)
|
||||
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, this)
|
||||
}
|
||||
super.finish()
|
||||
}
|
||||
|
||||
@@ -441,7 +561,7 @@ class EntryActivity : LockingHideActivity() {
|
||||
const val KEY_ENTRY = "KEY_ENTRY"
|
||||
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
|
||||
|
||||
fun launch(activity: Activity, entry: EntryVersioned, readOnly: Boolean, historyPosition: Int? = null) {
|
||||
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, entry.nodeId)
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
/*
|
||||
* 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.app.DatePickerDialog
|
||||
import android.app.TimePickerDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
@@ -26,43 +28,60 @@ import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ScrollView
|
||||
import android.widget.DatePicker
|
||||
import android.widget.TimePicker
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.ActionMenuView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import com.kunzisoft.keepass.R
|
||||
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.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.activities.dialogs.*
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.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.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
||||
import com.kunzisoft.keepass.view.showActionError
|
||||
import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
|
||||
class EntryEditActivity : LockingHideActivity(),
|
||||
class EntryEditActivity : LockingActivity(),
|
||||
IconPickerDialogFragment.IconPickerListener,
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener {
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||
SetOTPDialogFragment.CreateOtpListener,
|
||||
DatePickerDialog.OnDateSetListener,
|
||||
TimePickerDialog.OnTimeSetListener {
|
||||
|
||||
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 scrollView: ScrollView? = null
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var scrollView: NestedScrollView? = null
|
||||
private var entryEditContentsView: EntryEditContentsView? = null
|
||||
private var entryEditAddToolBar: ActionMenuView? = null
|
||||
private var saveView: View? = null
|
||||
|
||||
// Dialog thread
|
||||
private var progressDialogThread: ProgressDialogThread? = null
|
||||
private var lockView: View? = null
|
||||
|
||||
// Education
|
||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||
@@ -77,19 +96,40 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
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
|
||||
|
||||
entryEditContentsView = findViewById(R.id.entry_edit_contents)
|
||||
entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
||||
entryEditContentsView?.onDateClickListener = View.OnClickListener {
|
||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
||||
val dateTime = DateTime(expiresDate)
|
||||
val defaultYear = dateTime.year
|
||||
val defaultMonth = dateTime.monthOfYear-1
|
||||
val defaultDay = dateTime.dayOfMonth
|
||||
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
|
||||
.show(supportFragmentManager, "DatePickerFragment")
|
||||
}
|
||||
}
|
||||
|
||||
lockView = findViewById(R.id.lock_button)
|
||||
lockView?.setOnClickListener {
|
||||
lockAndExit()
|
||||
}
|
||||
|
||||
// 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<UUID>>(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)
|
||||
@@ -109,7 +149,7 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
|| !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.removeParent()
|
||||
}
|
||||
@@ -118,7 +158,7 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
}
|
||||
|
||||
// Parent is retrieve, it's a new entry to create
|
||||
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let {
|
||||
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
|
||||
mIsNew = true
|
||||
// Create an empty new entry
|
||||
if (savedInstanceState == null
|
||||
@@ -152,20 +192,51 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
// Add listener to the icon
|
||||
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
|
||||
|
||||
// Generate password button
|
||||
entryEditContentsView?.setOnPasswordGeneratorClickListener { openPasswordGenerator() }
|
||||
// Bottom Bar
|
||||
entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
|
||||
entryEditAddToolBar?.apply {
|
||||
menuInflater.inflate(R.menu.entry_edit, menu)
|
||||
|
||||
menu.findItem(R.id.menu_add_field).apply {
|
||||
val allowCustomField = mNewEntry?.allowCustomFields() == true
|
||||
isEnabled = allowCustomField
|
||||
isVisible = allowCustomField
|
||||
}
|
||||
|
||||
menu.findItem(R.id.menu_add_otp).apply {
|
||||
val allowOTP = mDatabase?.allowOTP == true
|
||||
isEnabled = allowOTP
|
||||
isVisible = allowOTP
|
||||
}
|
||||
|
||||
setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
R.id.menu_generate_password -> {
|
||||
openPasswordGenerator()
|
||||
true
|
||||
}
|
||||
R.id.menu_add_field -> {
|
||||
addNewCustomField()
|
||||
true
|
||||
}
|
||||
R.id.menu_add_otp -> {
|
||||
setupOTP()
|
||||
true
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save button
|
||||
saveView = findViewById(R.id.entry_edit_save)
|
||||
saveView = findViewById(R.id.entry_edit_validate)
|
||||
saveView?.setOnClickListener { saveEntry() }
|
||||
|
||||
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) { addNewCustomField() }
|
||||
|
||||
// Verify the education views
|
||||
entryEditActivityEducation = EntryEditActivityEducation(this)
|
||||
|
||||
// Create progress dialog
|
||||
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
|
||||
mProgressDialogThread?.onActionFinish = { actionTask, result ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_CREATE_ENTRY_TASK,
|
||||
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
||||
@@ -173,10 +244,21 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
finish()
|
||||
}
|
||||
}
|
||||
coordinatorLayout?.showActionError(result)
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateViewsWithEntry(newEntry: Entry) {
|
||||
// Don't start the field reference manager, we want to see the raw ref
|
||||
mDatabase?.stopManageEntry(newEntry)
|
||||
|
||||
@@ -189,16 +271,19 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
|
||||
url = newEntry.url
|
||||
password = newEntry.password
|
||||
expires = newEntry.expires
|
||||
if (expires)
|
||||
expiresDate = newEntry.expiryTime
|
||||
notes = newEntry.notes
|
||||
for (entry in newEntry.customFields.entries) {
|
||||
post {
|
||||
addNewCustomField(entry.key, entry.value)
|
||||
putCustomField(entry.key, entry.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateEntryWithViews(newEntry: EntryVersioned) {
|
||||
private fun populateEntryWithViews(newEntry: Entry) {
|
||||
|
||||
mDatabase?.startManageEntry(newEntry)
|
||||
|
||||
@@ -210,9 +295,13 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
username = entryView.username
|
||||
url = entryView.url
|
||||
password = entryView.password
|
||||
notes = entryView.notes
|
||||
expires = entryView.expires
|
||||
if (entryView.expires) {
|
||||
expiryTime = entryView.expiresDate
|
||||
}
|
||||
notes = entryView. notes
|
||||
entryView.customFields.forEach { customField ->
|
||||
addExtraField(customField.name, customField.protectedValue)
|
||||
putExtraField(customField.name, customField.protectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +309,7 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
mDatabase?.stopManageEntry(newEntry)
|
||||
}
|
||||
|
||||
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) {
|
||||
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
|
||||
mNewEntry?.icon = icon
|
||||
mDatabase?.drawFactory?.let { iconDrawFactory ->
|
||||
entryEditContentsView?.setIcon(iconDrawFactory, icon)
|
||||
@@ -238,7 +327,14 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
* Add a new customized field view and scroll to bottom
|
||||
*/
|
||||
private fun addNewCustomField() {
|
||||
entryEditContentsView?.addNewCustomField()
|
||||
entryEditContentsView?.addEmptyCustomField()
|
||||
}
|
||||
|
||||
private fun setupOTP() {
|
||||
// Retrieve the current otpElement if exists
|
||||
// and open the dialog to set up the OTP
|
||||
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
|
||||
.show(supportFragmentManager, "addOTPDialog")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,26 +350,26 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
// WARNING Add the parent previously deleted
|
||||
newEntry.parent = mEntry?.parent
|
||||
// Build info
|
||||
newEntry.lastAccessTime = PwDate()
|
||||
newEntry.lastModificationTime = PwDate()
|
||||
newEntry.lastAccessTime = DateInstant()
|
||||
newEntry.lastModificationTime = DateInstant()
|
||||
|
||||
populateEntryWithViews(newEntry)
|
||||
|
||||
// Open a progress dialog and save entry
|
||||
if (mIsNew) {
|
||||
mParent?.let { parent ->
|
||||
progressDialogThread?.startDatabaseCreateEntry(
|
||||
mProgressDialogThread?.startDatabaseCreateEntry(
|
||||
newEntry,
|
||||
parent,
|
||||
!mReadOnly
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
} else {
|
||||
mEntry?.let { oldEntry ->
|
||||
progressDialogThread?.startDatabaseUpdateEntry(
|
||||
mProgressDialogThread?.startDatabaseUpdateEntry(
|
||||
oldEntry,
|
||||
newEntry,
|
||||
!mReadOnly
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -281,23 +377,13 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
progressDialogThread?.registerProgressTask()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
progressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
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)
|
||||
|
||||
entryEditActivityEducation?.let {
|
||||
@@ -308,12 +394,10 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
}
|
||||
|
||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||
val passwordView = entryEditContentsView?.generatePasswordView
|
||||
val addNewFieldView = entryEditContentsView?.addNewFieldView
|
||||
|
||||
val generatePasswordEducationPerformed = passwordView != null
|
||||
val passwordGeneratorView: View? = entryEditAddToolBar?.findViewById(R.id.menu_generate_password)
|
||||
val generatePasswordEducationPerformed = passwordGeneratorView != null
|
||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||
passwordView,
|
||||
passwordGeneratorView,
|
||||
{
|
||||
openPasswordGenerator()
|
||||
},
|
||||
@@ -322,41 +406,95 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
}
|
||||
)
|
||||
if (!generatePasswordEducationPerformed) {
|
||||
// entryNewFieldEducationPerformed
|
||||
mNewEntry != null && mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
|
||||
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
|
||||
val addNewFieldEducationPerformed = mNewEntry != null
|
||||
&& mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
|
||||
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||
addNewFieldView,
|
||||
{
|
||||
addNewCustomField()
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryEditActivityEducation)
|
||||
}
|
||||
)
|
||||
if (!addNewFieldEducationPerformed) {
|
||||
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
|
||||
setupOtpView != null && setupOtpView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
|
||||
setupOtpView,
|
||||
{
|
||||
setupOTP()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_lock -> {
|
||||
lockAndExit()
|
||||
return true
|
||||
R.id.menu_save_database -> {
|
||||
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||
}
|
||||
|
||||
R.id.menu_contribute -> {
|
||||
MenuUtil.onContributionItemSelected(this)
|
||||
return true
|
||||
}
|
||||
|
||||
android.R.id.home -> finish()
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDateSet(datePicker: DatePicker?, year: Int, month: Int, day: Int) {
|
||||
// To fix android 4.4 issue
|
||||
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
|
||||
if (datePicker?.isShown == true) {
|
||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
||||
// Save the date
|
||||
entryEditContentsView?.expiresDate =
|
||||
DateInstant(DateTime(expiresDate)
|
||||
.withYear(year)
|
||||
.withMonthOfYear(month + 1)
|
||||
.withDayOfMonth(day)
|
||||
.toDate())
|
||||
// Launch the time picker
|
||||
val dateTime = DateTime(expiresDate)
|
||||
val defaultHour = dateTime.hourOfDay
|
||||
val defaultMinute = dateTime.minuteOfHour
|
||||
TimePickerFragment.getInstance(defaultHour, defaultMinute)
|
||||
.show(supportFragmentManager, "TimePickerFragment")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
|
||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
||||
// Save the date
|
||||
entryEditContentsView?.expiresDate =
|
||||
DateInstant(DateTime(expiresDate)
|
||||
.withHourOfDay(hours)
|
||||
.withMinuteOfHour(minutes)
|
||||
.toDate())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
mNewEntry?.let {
|
||||
populateEntryWithViews(it)
|
||||
@@ -380,6 +518,15 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.discard_changes)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.discard) { _, _ ->
|
||||
super@EntryEditActivity.onBackPressed()
|
||||
}.create().show()
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
// Assign entry callback as a result in all case
|
||||
try {
|
||||
@@ -424,7 +571,7 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
* @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)
|
||||
@@ -438,7 +585,7 @@ class EntryEditActivity : LockingHideActivity(),
|
||||
* @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,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
|
||||
@@ -26,35 +26,34 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
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.BrowserDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
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.autofill.AutofillHelper.KEY_SEARCH_INFO
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import kotlinx.android.synthetic.main.activity_file_selection.*
|
||||
import java.io.FileNotFoundException
|
||||
@@ -63,7 +62,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
|
||||
// Views
|
||||
private var fileListContainer: View? = null
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var fileManagerExplanationButton: View? = null
|
||||
private var createButtonView: View? = null
|
||||
private var openDatabaseButtonView: View? = null
|
||||
|
||||
@@ -76,7 +76,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private var progressDialogThread: ProgressDialogThread? = null
|
||||
private var mProgressDialogThread: ProgressDialogThread? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -84,31 +84,38 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
|
||||
|
||||
setContentView(R.layout.activity_file_selection)
|
||||
fileListContainer = findViewById(R.id.container_file_list)
|
||||
coordinatorLayout = findViewById(R.id.activity_file_selection_coordinator_layout)
|
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
toolbar.title = ""
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
fileManagerExplanationButton = findViewById(R.id.file_manager_explanation_button)
|
||||
fileManagerExplanationButton?.setOnClickListener {
|
||||
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
|
||||
}
|
||||
|
||||
// Create button
|
||||
createButtonView = findViewById(R.id.create_database_button)
|
||||
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/x-keepass"
|
||||
}.resolveActivity(packageManager) == null) {
|
||||
// No Activity found that can handle this intent.
|
||||
createButtonView?.visibility = View.GONE
|
||||
}
|
||||
else{
|
||||
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)
|
||||
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
|
||||
openDatabaseButtonView?.apply {
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
||||
setOnClickListener(it)
|
||||
setOnLongClickListener(it)
|
||||
}
|
||||
}
|
||||
|
||||
// History list
|
||||
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
|
||||
@@ -123,7 +130,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
databaseFileUri,
|
||||
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
|
||||
}
|
||||
updateFileListVisibility()
|
||||
}
|
||||
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
||||
// Remove from app database
|
||||
@@ -132,7 +138,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
|
||||
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
updateFileListVisibility()
|
||||
}
|
||||
}
|
||||
true
|
||||
@@ -146,8 +151,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
if (!(savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(EXTRA_STAY)
|
||||
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val databasePath = prefs.getString(PasswordActivity.KEY_DEFAULT_DATABASE_PATH, "")
|
||||
val databasePath = PreferencesUtil.getDefaultDatabasePath(this)
|
||||
|
||||
UriUtil.parse(databasePath)?.let { databaseFileUri ->
|
||||
launchPasswordActivityWithPath(databaseFileUri)
|
||||
@@ -163,13 +167,12 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
}
|
||||
|
||||
// Attach the dialog thread to this activity
|
||||
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
|
||||
mProgressDialogThread = ProgressDialogThread(this).apply {
|
||||
onActionFinish = { actionTask, _ ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_CREATE_TASK -> {
|
||||
// TODO Check
|
||||
// mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
// updateFileListVisibility()
|
||||
GroupActivity.launch(this)
|
||||
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,23 +183,15 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun createNewFile() {
|
||||
try {
|
||||
startActivityForResult(Intent(
|
||||
Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/x-keepass"
|
||||
putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
|
||||
getString(R.string.database_file_extension_default))
|
||||
},
|
||||
CREATE_FILE_REQUEST_CODE)
|
||||
} catch (e: Exception) {
|
||||
BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
|
||||
}
|
||||
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)
|
||||
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
Log.e(TAG, error, e)
|
||||
}
|
||||
|
||||
@@ -224,7 +219,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
try {
|
||||
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
||||
databaseUri, keyFile,
|
||||
assistStructure)
|
||||
assistStructure,
|
||||
intent.getParcelableExtra(KEY_SEARCH_INFO))
|
||||
} catch (e: FileNotFoundException) {
|
||||
fileNoFoundAction(e)
|
||||
}
|
||||
@@ -236,16 +232,21 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
private fun launchGroupActivity(readOnly: Boolean) {
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
GroupActivity.launch(this@FileDatabaseSelectActivity, readOnly)
|
||||
GroupActivity.launch(this@FileDatabaseSelectActivity,
|
||||
readOnly)
|
||||
},
|
||||
{
|
||||
GroupActivity.launchForKeyboardSelection(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)
|
||||
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
||||
assistStructure,
|
||||
intent.getParcelableExtra(KEY_SEARCH_INFO),
|
||||
readOnly)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -257,25 +258,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
overridePendingTransition(0, 0)
|
||||
}
|
||||
|
||||
private fun updateExternalStorageWarning() {
|
||||
// To show errors
|
||||
var warning = -1
|
||||
val state = Environment.getExternalStorageState()
|
||||
if (state == Environment.MEDIA_MOUNTED_READ_ONLY) {
|
||||
warning = R.string.read_only_warning
|
||||
} else if (state != Environment.MEDIA_MOUNTED) {
|
||||
warning = R.string.warning_unmounted
|
||||
}
|
||||
|
||||
val labelWarningView = findViewById<TextView>(R.id.label_warning)
|
||||
if (warning != -1) {
|
||||
labelWarningView.setText(warning)
|
||||
labelWarningView.visibility = View.VISIBLE
|
||||
} else {
|
||||
labelWarningView.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val database = Database.getInstance()
|
||||
if (database.loaded) {
|
||||
@@ -284,24 +266,35 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
|
||||
super.onResume()
|
||||
|
||||
updateExternalStorageWarning()
|
||||
|
||||
// Construct adapter with listeners
|
||||
if (PreferencesUtil.showRecentFiles(this)) {
|
||||
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
|
||||
databaseFileHistoryList?.let {
|
||||
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
|
||||
updateFileListVisibility()
|
||||
databaseFileHistoryList?.let { historyList ->
|
||||
val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity)
|
||||
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(
|
||||
// Show only uri accessible
|
||||
historyList.filter {
|
||||
if (hideBrokenLocations) {
|
||||
FileDatabaseInfo(this@FileDatabaseSelectActivity,
|
||||
it.databaseUri).exists
|
||||
} else
|
||||
true
|
||||
})
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
|
||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
// Register progress task
|
||||
progressDialogThread?.registerProgressTask()
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
// Unregister progress task
|
||||
progressDialogThread?.unregisterProgressTask()
|
||||
mProgressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
@@ -314,13 +307,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
|
||||
}
|
||||
|
||||
private fun updateFileListVisibility() {
|
||||
if (mAdapterDatabaseHistory?.itemCount == 0)
|
||||
fileListContainer?.visibility = View.INVISIBLE
|
||||
else
|
||||
fileListContainer?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onAssignKeyDialogPositiveClick(
|
||||
masterPasswordChecked: Boolean, masterPassword: String?,
|
||||
keyFileChecked: Boolean, keyFile: Uri?) {
|
||||
@@ -329,7 +315,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
mDatabaseFileUri?.let { databaseUri ->
|
||||
|
||||
// Create the new database
|
||||
progressDialogThread?.startDatabaseCreate(
|
||||
mProgressDialogThread?.startDatabaseCreate(
|
||||
databaseUri,
|
||||
masterPasswordChecked,
|
||||
masterPassword,
|
||||
@@ -365,15 +351,18 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
}
|
||||
|
||||
// Retrieve the created URI from the file manager
|
||||
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
|
||||
mDatabaseFileUri = data?.data
|
||||
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
|
||||
mDatabaseFileUri = databaseFileCreatedUri
|
||||
if (mDatabaseFileUri != null) {
|
||||
AssignMasterKeyDialogFragment.getInstance(true)
|
||||
.show(supportFragmentManager, "passwordDialog")
|
||||
} else {
|
||||
val error = getString(R.string.error_create_database)
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
Log.e(TAG, error)
|
||||
}
|
||||
// else {
|
||||
// TODO Show error
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,8 +414,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
private const val EXTRA_STAY = "EXTRA_STAY"
|
||||
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
|
||||
|
||||
private const val CREATE_FILE_REQUEST_CODE = 3853
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
* No Standard Launch, pass by PasswordActivity
|
||||
@@ -450,10 +437,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
||||
*/
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure) {
|
||||
fun launchForAutofillResult(activity: Activity,
|
||||
assistStructure: AssistStructure,
|
||||
searchInfo: SearchInfo?) {
|
||||
AutofillHelper.startActivityForAutofillResult(activity,
|
||||
Intent(activity, FileDatabaseSelectActivity::class.java),
|
||||
assistStructure)
|
||||
assistStructure,
|
||||
searchInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,21 +42,27 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.ReadOnlyDialog
|
||||
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
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.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.education.GroupActivityEducation
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||
@@ -71,17 +77,20 @@ import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.view.AddNodeButtonView
|
||||
import com.kunzisoft.keepass.view.ToolbarAction
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import com.kunzisoft.keepass.view.showActionError
|
||||
|
||||
class GroupActivity : LockingActivity(),
|
||||
GroupEditDialogFragment.EditGroupListener,
|
||||
IconPickerDialogFragment.IconPickerListener,
|
||||
ListNodesFragment.NodeClickListener,
|
||||
ListNodesFragment.NodesActionMenuListener,
|
||||
DeleteNodesDialogFragment.DeleteNodeListener,
|
||||
ListNodesFragment.OnScrollListener,
|
||||
SortDialogFragment.SortSelectionListener {
|
||||
|
||||
// Views
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var lockView: View? = null
|
||||
private var toolbar: Toolbar? = null
|
||||
private var searchTitleView: View? = null
|
||||
private var toolbarAction: ToolbarAction? = null
|
||||
@@ -95,15 +104,12 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
private var mListNodesFragment: ListNodesFragment? = null
|
||||
private var mCurrentGroupIsASearch: Boolean = false
|
||||
|
||||
private var progressDialogThread: ProgressDialogThread? = null
|
||||
private var mRequestStartupSearch = true
|
||||
|
||||
// Nodes
|
||||
private var mRootGroup: GroupVersioned? = null
|
||||
private var mCurrentGroup: GroupVersioned? = null
|
||||
private var mOldGroupToUpdate: GroupVersioned? = null
|
||||
// TODO private var mNodeToCopy: NodeVersioned? = null
|
||||
// TODO private var mNodeToMove: NodeVersioned? = null
|
||||
private var mRootGroup: Group? = null
|
||||
private var mCurrentGroup: Group? = null
|
||||
private var mOldGroupToUpdate: Group? = null
|
||||
|
||||
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
|
||||
|
||||
@@ -130,23 +136,22 @@ class GroupActivity : LockingActivity(),
|
||||
groupNameView = findViewById(R.id.group_name)
|
||||
toolbarAction = findViewById(R.id.toolbar_action)
|
||||
modeTitleView = findViewById(R.id.mode_title_view)
|
||||
lockView = findViewById(R.id.lock_button)
|
||||
|
||||
lockView?.setOnClickListener {
|
||||
lockAndExit()
|
||||
}
|
||||
|
||||
toolbar?.title = ""
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
/*
|
||||
toolbarAction?.setNavigationOnClickListener {
|
||||
toolbarAction?.collapse()
|
||||
mNodeToCopy = null
|
||||
mNodeToMove = null
|
||||
}
|
||||
*/
|
||||
|
||||
// Focus view to reinitialize timeout
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
|
||||
|
||||
// Retrieve elements after an orientation change
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.containsKey(REQUEST_STARTUP_SEARCH_KEY))
|
||||
mRequestStartupSearch = savedInstanceState.getBoolean(REQUEST_STARTUP_SEARCH_KEY)
|
||||
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
|
||||
mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY)
|
||||
}
|
||||
@@ -207,13 +212,13 @@ class GroupActivity : LockingActivity(),
|
||||
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database)
|
||||
|
||||
// Init dialog thread
|
||||
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
|
||||
mProgressDialogThread?.onActionFinish = { actionTask, result ->
|
||||
|
||||
var oldNodes: List<NodeVersioned> = ArrayList()
|
||||
var oldNodes: List<Node> = ArrayList()
|
||||
result.data?.getBundle(OLD_NODES_KEY)?.let { oldNodesBundle ->
|
||||
oldNodes = getListNodesFromBundle(database, oldNodesBundle)
|
||||
}
|
||||
var newNodes: List<NodeVersioned> = ArrayList()
|
||||
var newNodes: List<Node> = ArrayList()
|
||||
result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle ->
|
||||
newNodes = getListNodesFromBundle(database, newNodesBundle)
|
||||
}
|
||||
@@ -234,39 +239,34 @@ class GroupActivity : LockingActivity(),
|
||||
ACTION_DATABASE_DELETE_NODES_TASK -> {
|
||||
if (result.isSuccess) {
|
||||
|
||||
// Rebuild all the list the avoid bug when delete node from db sort
|
||||
if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB) {
|
||||
// Rebuild all the list to avoid bug when delete node from sort
|
||||
mListNodesFragment?.rebuildList()
|
||||
} else {
|
||||
// Use the old Nodes / entries unchanged with the old parent
|
||||
mListNodesFragment?.removeNodes(oldNodes)
|
||||
}
|
||||
|
||||
// Add trash in views list if it doesn't exists
|
||||
if (database.isRecycleBinEnabled) {
|
||||
val recycleBin = database.recycleBin
|
||||
if (mCurrentGroup != null && recycleBin != null
|
||||
&& mCurrentGroup!!.parent == null
|
||||
&& mCurrentGroup != recycleBin) {
|
||||
if (mListNodesFragment?.contains(recycleBin) == true)
|
||||
val currentGroup = mCurrentGroup
|
||||
if (currentGroup != null && recycleBin != null
|
||||
&& currentGroup != recycleBin) {
|
||||
// Recycle bin already here, simply update it
|
||||
if (mListNodesFragment?.contains(recycleBin) == true) {
|
||||
mListNodesFragment?.updateNode(recycleBin)
|
||||
else
|
||||
}
|
||||
// Recycle bin not here, verify if parents are similar to add it
|
||||
else if (currentGroup == recycleBin.parent) {
|
||||
mListNodesFragment?.addNode(recycleBin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.isSuccess) {
|
||||
result.exception?.errorId?.let { errorId ->
|
||||
coordinatorLayout?.let { coordinatorLayout ->
|
||||
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
coordinatorLayout?.showActionError(result)
|
||||
|
||||
finishNodeAction()
|
||||
|
||||
refreshNumberOfChildren()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun openSearchGroup(group: GroupVersioned?) {
|
||||
private fun openSearchGroup(group: Group?) {
|
||||
// Delete the previous search fragment
|
||||
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
|
||||
if (searchFragment != null) {
|
||||
@@ -301,11 +301,11 @@ class GroupActivity : LockingActivity(),
|
||||
openGroup(group, true)
|
||||
}
|
||||
|
||||
private fun openChildGroup(group: GroupVersioned) {
|
||||
private fun openChildGroup(group: Group) {
|
||||
openGroup(group, false)
|
||||
}
|
||||
|
||||
private fun openGroup(group: GroupVersioned?, isASearch: Boolean) {
|
||||
private fun openGroup(group: Group?, isASearch: Boolean) {
|
||||
// Check TimeoutHelper
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
||||
// Open a group in a new fragment
|
||||
@@ -342,21 +342,23 @@ class GroupActivity : LockingActivity(),
|
||||
mOldGroupToUpdate?.let {
|
||||
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, it)
|
||||
}
|
||||
outState.putBoolean(REQUEST_STARTUP_SEARCH_KEY, mRequestStartupSearch)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): GroupVersioned? {
|
||||
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): Group? {
|
||||
|
||||
// Force read only if the database is like that
|
||||
mReadOnly = mDatabase?.isReadOnly == true || mReadOnly
|
||||
|
||||
// If it's a search
|
||||
if (Intent.ACTION_SEARCH == intent.action) {
|
||||
return mDatabase?.search(intent.getStringExtra(SearchManager.QUERY).trim { it <= ' ' })
|
||||
val searchString = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
|
||||
return mDatabase?.createVirtualGroupFromSearch(searchString)
|
||||
}
|
||||
// else a real group
|
||||
else {
|
||||
var pwGroupId: PwNodeId<*>? = null
|
||||
var pwGroupId: NodeId<*>? = null
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) {
|
||||
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY)
|
||||
} else {
|
||||
@@ -365,7 +367,7 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
Log.w(TAG, "Creating tree view")
|
||||
val currentGroup: GroupVersioned?
|
||||
val currentGroup: Group?
|
||||
currentGroup = if (pwGroupId == null) {
|
||||
mRootGroup
|
||||
} else {
|
||||
@@ -422,14 +424,7 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
// Assign number of children
|
||||
numberChildrenView?.apply {
|
||||
if (PreferencesUtil.showNumberEntries(context)) {
|
||||
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
|
||||
visibility = View.VISIBLE
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
refreshNumberOfChildren()
|
||||
|
||||
// Show selection mode message if needed
|
||||
if (mSelectionMode) {
|
||||
@@ -445,35 +440,41 @@ class GroupActivity : LockingActivity(),
|
||||
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
|
||||
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
|
||||
mCurrentGroup?.let {
|
||||
val isRoot = it == mRootGroup
|
||||
if (!it.allowAddEntryIfIsRoot())
|
||||
addEntryEnabled = !isRoot && addEntryEnabled
|
||||
if (isRoot) {
|
||||
showWarnings()
|
||||
}
|
||||
addEntryEnabled = it != mRootGroup && addEntryEnabled
|
||||
}
|
||||
enableAddGroup(addGroupEnabled)
|
||||
enableAddEntry(addEntryEnabled)
|
||||
|
||||
if (isEnable)
|
||||
showButton()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshNumberOfChildren() {
|
||||
numberChildrenView?.apply {
|
||||
if (PreferencesUtil.showNumberEntries(context)) {
|
||||
text = mCurrentGroup?.getNumberOfChildEntries(*Group.ChildFilter.getDefaults(context))?.toString() ?: ""
|
||||
visibility = View.VISIBLE
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScrolled(dy: Int) {
|
||||
addNodeButtonView?.hideButtonOnScrollListener(dy)
|
||||
}
|
||||
|
||||
override fun onNodeClick(node: NodeVersioned) {
|
||||
override fun onNodeClick(node: Node) {
|
||||
when (node.type) {
|
||||
Type.GROUP -> try {
|
||||
openChildGroup(node as GroupVersioned)
|
||||
openChildGroup(node as Group)
|
||||
} catch (e: ClassCastException) {
|
||||
Log.e(TAG, "Node can't be cast in Group")
|
||||
}
|
||||
|
||||
Type.ENTRY -> try {
|
||||
val entryVersioned = node as EntryVersioned
|
||||
val entryVersioned = node as Entry
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
|
||||
@@ -491,8 +492,10 @@ class GroupActivity : LockingActivity(),
|
||||
{
|
||||
// Build response with the entry selected
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
|
||||
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity,
|
||||
entryVersioned.getEntryInfo(mDatabase!!))
|
||||
mDatabase?.let { database ->
|
||||
AutofillHelper.buildResponse(this@GroupActivity,
|
||||
entryVersioned.getEntryInfo(database))
|
||||
}
|
||||
}
|
||||
finish()
|
||||
})
|
||||
@@ -507,9 +510,10 @@ class GroupActivity : LockingActivity(),
|
||||
private fun finishNodeAction() {
|
||||
actionNodeMode?.finish()
|
||||
actionNodeMode = null
|
||||
addNodeButtonView?.showButton()
|
||||
}
|
||||
|
||||
override fun onNodeSelected(nodes: List<NodeVersioned>): Boolean {
|
||||
override fun onNodeSelected(nodes: List<Node>): Boolean {
|
||||
if (nodes.isNotEmpty()) {
|
||||
if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) {
|
||||
mListNodesFragment?.actionNodesCallback(nodes, this)?.let {
|
||||
@@ -518,40 +522,41 @@ class GroupActivity : LockingActivity(),
|
||||
} else {
|
||||
actionNodeMode?.invalidate()
|
||||
}
|
||||
addNodeButtonView?.hideButton()
|
||||
} else {
|
||||
finishNodeAction()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOpenMenuClick(node: NodeVersioned): Boolean {
|
||||
override fun onOpenMenuClick(node: Node): Boolean {
|
||||
finishNodeAction()
|
||||
onNodeClick(node)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onEditMenuClick(node: NodeVersioned): Boolean {
|
||||
override fun onEditMenuClick(node: Node): Boolean {
|
||||
finishNodeAction()
|
||||
when (node.type) {
|
||||
Type.GROUP -> {
|
||||
mOldGroupToUpdate = node as GroupVersioned
|
||||
mOldGroupToUpdate = node as Group
|
||||
GroupEditDialogFragment.build(mOldGroupToUpdate!!)
|
||||
.show(supportFragmentManager,
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP)
|
||||
}
|
||||
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as EntryVersioned)
|
||||
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as Entry)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean {
|
||||
override fun onCopyMenuClick(nodes: List<Node>): Boolean {
|
||||
actionNodeMode?.invalidate()
|
||||
|
||||
// Nothing here fragment calls onPasteMenuClick internally
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean {
|
||||
override fun onMoveMenuClick(nodes: List<Node>): Boolean {
|
||||
actionNodeMode?.invalidate()
|
||||
|
||||
// Nothing here fragment calls onPasteMenuClick internally
|
||||
@@ -559,56 +564,95 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
|
||||
nodes: List<NodeVersioned>): Boolean {
|
||||
nodes: List<Node>): Boolean {
|
||||
// Move or copy only if allowed (in root if allowed)
|
||||
if (mCurrentGroup != mDatabase?.rootGroup
|
||||
|| mDatabase?.rootCanContainsEntry() == true) {
|
||||
|
||||
when (pasteMode) {
|
||||
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
|
||||
// Copy
|
||||
mCurrentGroup?.let { newParent ->
|
||||
progressDialogThread?.startDatabaseCopyNodes(
|
||||
mProgressDialogThread?.startDatabaseCopyNodes(
|
||||
nodes,
|
||||
newParent,
|
||||
!mReadOnly
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
|
||||
// Move
|
||||
mCurrentGroup?.let { newParent ->
|
||||
progressDialogThread?.startDatabaseMoveNodes(
|
||||
mProgressDialogThread?.startDatabaseMoveNodes(
|
||||
nodes,
|
||||
newParent,
|
||||
!mReadOnly
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
coordinatorLayout?.let { coordinatorLayout ->
|
||||
Snackbar.make(coordinatorLayout,
|
||||
R.string.error_copy_entry_here,
|
||||
Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
finishNodeAction()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean {
|
||||
progressDialogThread?.startDatabaseDeleteNodes(
|
||||
override fun onDeleteMenuClick(nodes: List<Node>): Boolean {
|
||||
val database = mDatabase
|
||||
|
||||
// If recycle bin enabled, ensure it exists
|
||||
if (database != null && database.isRecycleBinEnabled) {
|
||||
database.ensureRecycleBinExists(resources)
|
||||
}
|
||||
|
||||
// If recycle bin enabled and not in recycle bin, move in recycle bin
|
||||
if (database != null
|
||||
&& database.isRecycleBinEnabled
|
||||
&& database.recycleBin != mCurrentGroup) {
|
||||
mProgressDialogThread?.startDatabaseDeleteNodes(
|
||||
nodes,
|
||||
!mReadOnly
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
// else open the dialog to confirm deletion
|
||||
else {
|
||||
DeleteNodesDialogFragment.getInstance(nodes)
|
||||
.show(supportFragmentManager, "deleteNodesDialogFragment")
|
||||
}
|
||||
finishNodeAction()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun permanentlyDeleteNodes(nodes: List<Node>) {
|
||||
mProgressDialogThread?.startDatabaseDeleteNodes(
|
||||
nodes,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Show the lock button
|
||||
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
// Refresh the elements
|
||||
assignGroupViewElements()
|
||||
// Refresh suggestions to change preferences
|
||||
mSearchSuggestionAdapter?.reInit(this)
|
||||
|
||||
progressDialogThread?.registerProgressTask()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
progressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
|
||||
finishNodeAction()
|
||||
@@ -618,20 +662,32 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
val inflater = menuInflater
|
||||
inflater.inflate(R.menu.search, menu)
|
||||
inflater.inflate(R.menu.database_lock, menu)
|
||||
inflater.inflate(R.menu.database, menu)
|
||||
if (mReadOnly) {
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
}
|
||||
if (!mSelectionMode) {
|
||||
inflater.inflate(R.menu.default_menu, menu)
|
||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||
}
|
||||
|
||||
// Menu for recycle bin
|
||||
if (!mReadOnly
|
||||
&& mDatabase?.isRecycleBinEnabled == true
|
||||
&& mDatabase?.recycleBin == mCurrentGroup) {
|
||||
inflater.inflate(R.menu.recycle_bin, menu)
|
||||
}
|
||||
|
||||
// Get the SearchView and set the searchable configuration
|
||||
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
|
||||
|
||||
menu.findItem(R.id.menu_search)?.let {
|
||||
val searchView = it.actionView as SearchView?
|
||||
searchView?.apply {
|
||||
setSearchableInfo(searchManager.getSearchableInfo(
|
||||
ComponentName(this@GroupActivity, GroupActivity::class.java)))
|
||||
(searchManager?.getSearchableInfo(
|
||||
ComponentName(this@GroupActivity, GroupActivity::class.java)))?.let { searchableInfo ->
|
||||
setSearchableInfo(searchableInfo)
|
||||
}
|
||||
setIconifiedByDefault(false) // Do not iconify the widget; expand it by default
|
||||
suggestionsAdapter = mSearchSuggestionAdapter
|
||||
setOnSuggestionListener(object : SearchView.OnSuggestionListener {
|
||||
@@ -649,6 +705,13 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
})
|
||||
}
|
||||
// Expand the search view if defined in settings
|
||||
if (mRequestStartupSearch
|
||||
&& PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
|
||||
// To request search only one time
|
||||
mRequestStartupSearch = false
|
||||
it.expandActionView()
|
||||
}
|
||||
}
|
||||
|
||||
super.onCreateOptionsMenu(menu)
|
||||
@@ -704,12 +767,11 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
if (!sortMenuEducationPerformed) {
|
||||
// lockMenuEducationPerformed
|
||||
toolbar != null
|
||||
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null
|
||||
&& groupActivityEducation.checkAndPerformedLockMenuEducation(
|
||||
toolbar!!.findViewById(R.id.menu_lock),
|
||||
val lockButtonView = findViewById<View>(R.id.lock_button_icon)
|
||||
lockButtonView != null
|
||||
&& groupActivityEducation.checkAndPerformedLockMenuEducation(lockButtonView,
|
||||
{
|
||||
onOptionsItemSelected(menu.findItem(R.id.menu_lock))
|
||||
lockAndExit()
|
||||
},
|
||||
{
|
||||
performedNextEducation(groupActivityEducation, menu)
|
||||
@@ -728,8 +790,15 @@ class GroupActivity : LockingActivity(),
|
||||
R.id.menu_search ->
|
||||
//onSearchRequested();
|
||||
return true
|
||||
R.id.menu_lock -> {
|
||||
lockAndExit()
|
||||
R.id.menu_save_database -> {
|
||||
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||
return true
|
||||
}
|
||||
R.id.menu_empty_recycle_bin -> {
|
||||
mCurrentGroup?.getChildren()?.let { listChildren ->
|
||||
// Automatically delete all elements
|
||||
onDeleteMenuClick(listChildren)
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
@@ -742,7 +811,7 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
|
||||
name: String?,
|
||||
icon: PwIcon?) {
|
||||
icon: IconImage?) {
|
||||
|
||||
if (name != null && name.isNotEmpty() && icon != null) {
|
||||
when (action) {
|
||||
@@ -756,28 +825,33 @@ class GroupActivity : LockingActivity(),
|
||||
// Not really needed here because added in runnable but safe
|
||||
newGroup.parent = currentGroup
|
||||
|
||||
progressDialogThread?.startDatabaseCreateGroup(
|
||||
newGroup, currentGroup, !mReadOnly)
|
||||
mProgressDialogThread?.startDatabaseCreateGroup(
|
||||
newGroup,
|
||||
currentGroup,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> {
|
||||
// If update add new elements
|
||||
mOldGroupToUpdate?.let { oldGroupToUpdate ->
|
||||
GroupVersioned(oldGroupToUpdate).let { updateGroup ->
|
||||
val updateGroup = Group(oldGroupToUpdate).let { updateGroup ->
|
||||
updateGroup.apply {
|
||||
// WARNING remove parent and children to keep memory
|
||||
removeParent()
|
||||
removeChildren() // TODO concurrent exception
|
||||
removeChildren()
|
||||
|
||||
title = name
|
||||
this.icon = icon // TODO custom icon
|
||||
}
|
||||
|
||||
// If group updated save it in the database
|
||||
progressDialogThread?.startDatabaseUpdateGroup(
|
||||
oldGroupToUpdate, updateGroup, !mReadOnly)
|
||||
}
|
||||
// If group updated save it in the database
|
||||
mProgressDialogThread?.startDatabaseUpdateGroup(
|
||||
oldGroupToUpdate,
|
||||
updateGroup,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
@@ -787,7 +861,7 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
|
||||
name: String?,
|
||||
icon: PwIcon?) {
|
||||
icon: IconImage?) {
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
@@ -798,16 +872,8 @@ class GroupActivity : LockingActivity(),
|
||||
.iconPicked(bundle)
|
||||
}
|
||||
|
||||
private fun showWarnings() {
|
||||
if (Database.getInstance().isReadOnly) {
|
||||
if (PreferencesUtil.showReadOnlyWarning(this)) {
|
||||
ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
|
||||
mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom)
|
||||
override fun onSortSelected(sortNodeEnum: SortNodeEnum, sortNodeParameters: SortNodeEnum.SortNodeParameters) {
|
||||
mListNodesFragment?.onSortSelected(sortNodeEnum, sortNodeParameters)
|
||||
}
|
||||
|
||||
override fun startActivity(intent: Intent) {
|
||||
@@ -851,8 +917,8 @@ class GroupActivity : LockingActivity(),
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
// Not directly get the entry from intent data but from database
|
||||
mListNodesFragment?.rebuildList()
|
||||
// Directly used the onActivityResult in fragment
|
||||
mListNodesFragment?.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private fun removeSearchInIntent(intent: Intent) {
|
||||
@@ -893,25 +959,48 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
private val TAG = GroupActivity::class.java.name
|
||||
|
||||
private const val REQUEST_STARTUP_SEARCH_KEY = "REQUEST_STARTUP_SEARCH_KEY"
|
||||
private const val GROUP_ID_KEY = "GROUP_ID_KEY"
|
||||
private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG"
|
||||
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
|
||||
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
|
||||
|
||||
private fun buildAndLaunchIntent(context: Context, group: GroupVersioned?, readOnly: Boolean,
|
||||
private fun buildIntent(context: Context,
|
||||
group: Group?,
|
||||
searchInfo: SearchInfo?,
|
||||
readOnly: Boolean,
|
||||
intentBuildLauncher: (Intent) -> Unit) {
|
||||
val checkTime = if (context is Activity)
|
||||
TimeoutHelper.checkTimeAndLockIfTimeout(context)
|
||||
else
|
||||
TimeoutHelper.checkTime(context)
|
||||
if (checkTime) {
|
||||
val intent = Intent(context, GroupActivity::class.java)
|
||||
if (group != null) {
|
||||
intent.putExtra(GROUP_ID_KEY, group.nodeId)
|
||||
}
|
||||
if (searchInfo != null) {
|
||||
intent.action = Intent.ACTION_SEARCH
|
||||
val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId
|
||||
intent.putExtra(SearchManager.QUERY, searchQuery)
|
||||
}
|
||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||
intentBuildLauncher.invoke(intent)
|
||||
}
|
||||
|
||||
private fun checkTimeAndBuildIntent(activity: Activity,
|
||||
group: Group?,
|
||||
searchInfo: SearchInfo?,
|
||||
readOnly: Boolean,
|
||||
intentBuildLauncher: (Intent) -> Unit) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
buildIntent(activity, group, searchInfo, readOnly, intentBuildLauncher)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTimeAndBuildIntent(context: Context,
|
||||
group: Group?,
|
||||
searchInfo: SearchInfo?,
|
||||
readOnly: Boolean,
|
||||
intentBuildLauncher: (Intent) -> Unit) {
|
||||
if (TimeoutHelper.checkTime(context)) {
|
||||
buildIntent(context, group, searchInfo, readOnly, intentBuildLauncher)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -919,11 +1008,9 @@ class GroupActivity : LockingActivity(),
|
||||
* Standard Launch
|
||||
* -------------------------
|
||||
*/
|
||||
|
||||
@JvmOverloads
|
||||
fun launch(context: Context, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
||||
TimeoutHelper.recordTime(context)
|
||||
buildAndLaunchIntent(context, null, readOnly) { intent ->
|
||||
fun launch(context: Context,
|
||||
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
||||
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -934,10 +1021,9 @@ class GroupActivity : LockingActivity(),
|
||||
* -------------------------
|
||||
*/
|
||||
// TODO implement pre search to directly open the direct group
|
||||
|
||||
fun launchForKeyboardSelection(context: Context, readOnly: Boolean) {
|
||||
TimeoutHelper.recordTime(context)
|
||||
buildAndLaunchIntent(context, null, readOnly) { intent ->
|
||||
fun launchForKeyboardSelection(context: Context,
|
||||
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
||||
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
|
||||
EntrySelectionHelper.startActivityForEntrySelection(context, intent)
|
||||
}
|
||||
}
|
||||
@@ -947,13 +1033,13 @@ class GroupActivity : LockingActivity(),
|
||||
* Autofill Launch
|
||||
* -------------------------
|
||||
*/
|
||||
// TODO implement pre search to directly open the direct group
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure, readOnly: Boolean) {
|
||||
TimeoutHelper.recordTime(activity)
|
||||
buildAndLaunchIntent(activity, null, readOnly) { intent ->
|
||||
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure)
|
||||
fun launchForAutofillResult(activity: Activity,
|
||||
assistStructure: AssistStructure,
|
||||
searchInfo: SearchInfo? = null,
|
||||
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
|
||||
checkTimeAndBuildIntent(activity, null, searchInfo, readOnly) { intent ->
|
||||
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure, searchInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
/*
|
||||
* 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
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.Log
|
||||
@@ -18,16 +35,16 @@ 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.Type
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import java.util.*
|
||||
|
||||
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
|
||||
@@ -35,8 +52,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
private var nodeClickListener: NodeClickListener? = null
|
||||
private var onScrollListener: OnScrollListener? = null
|
||||
|
||||
private var listView: RecyclerView? = null
|
||||
var mainGroup: GroupVersioned? = null
|
||||
private var mNodesRecyclerView: RecyclerView? = null
|
||||
var mainGroup: Group? = null
|
||||
private set
|
||||
private var mAdapter: NodeAdapter? = null
|
||||
|
||||
@@ -44,14 +61,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
private set
|
||||
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
|
||||
private set
|
||||
private val listActionNodes = LinkedList<NodeVersioned>()
|
||||
private val listPasteNodes = LinkedList<NodeVersioned>()
|
||||
private val listActionNodes = LinkedList<Node>()
|
||||
private val listPasteNodes = LinkedList<Node>()
|
||||
|
||||
private var notFoundView: View? = null
|
||||
private var isASearchResult: Boolean = false
|
||||
|
||||
// Preferences for sorting
|
||||
private var prefs: SharedPreferences? = null
|
||||
|
||||
private var readOnly: Boolean = false
|
||||
get() {
|
||||
@@ -103,7 +118,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
mAdapter = NodeAdapter(context)
|
||||
mAdapter?.apply {
|
||||
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
|
||||
override fun onNodeClick(node: NodeVersioned) {
|
||||
override fun onNodeClick(node: Node) {
|
||||
if (nodeActionSelectionMode) {
|
||||
if (listActionNodes.contains(node)) {
|
||||
// Remove selected item if already selected
|
||||
@@ -120,7 +135,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNodeLongClick(node: NodeVersioned): Boolean {
|
||||
override fun onNodeLongClick(node: Node): Boolean {
|
||||
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
|
||||
// Select the first item after a long click
|
||||
if (!listActionNodes.contains(node))
|
||||
@@ -136,7 +151,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
})
|
||||
}
|
||||
}
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -150,11 +164,17 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
// To apply theme
|
||||
val rootView = inflater.cloneInContext(contextThemed)
|
||||
.inflate(R.layout.fragment_list_nodes, container, false)
|
||||
listView = rootView.findViewById(R.id.nodes_list)
|
||||
mNodesRecyclerView = rootView.findViewById(R.id.nodes_list)
|
||||
notFoundView = rootView.findViewById(R.id.not_found_container)
|
||||
|
||||
mNodesRecyclerView?.apply {
|
||||
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = mAdapter
|
||||
}
|
||||
|
||||
onScrollListener?.let { onScrollListener ->
|
||||
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
mNodesRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
onScrollListener.onScrolled(dy)
|
||||
@@ -162,8 +182,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
})
|
||||
}
|
||||
|
||||
rebuildList()
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
@@ -175,14 +193,14 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
|
||||
// Refresh data
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
rebuildList()
|
||||
|
||||
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
|
||||
// To show the " no search entry found "
|
||||
listView?.visibility = View.GONE
|
||||
mNodesRecyclerView?.visibility = View.GONE
|
||||
notFoundView?.visibility = View.VISIBLE
|
||||
} else {
|
||||
listView?.visibility = View.VISIBLE
|
||||
mNodesRecyclerView?.visibility = View.VISIBLE
|
||||
notFoundView?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
@@ -190,30 +208,27 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
fun rebuildList() {
|
||||
// Add elements to the list
|
||||
mainGroup?.let { mainGroup ->
|
||||
mAdapter?.rebuildList(mainGroup)
|
||||
mAdapter?.apply {
|
||||
rebuildList(mainGroup)
|
||||
// To visually change the elements
|
||||
if (PreferencesUtil.APPEARANCE_CHANGED) {
|
||||
notifyDataSetChanged()
|
||||
PreferencesUtil.APPEARANCE_CHANGED = false
|
||||
}
|
||||
}
|
||||
listView?.apply {
|
||||
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = mAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
|
||||
// Toggle setting
|
||||
prefs?.edit()?.apply {
|
||||
putString(getString(R.string.sort_node_key), sortNodeEnum.name)
|
||||
putBoolean(getString(R.string.sort_ascending_key), ascending)
|
||||
putBoolean(getString(R.string.sort_group_before_key), groupsBefore)
|
||||
putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom)
|
||||
apply()
|
||||
override fun onSortSelected(sortNodeEnum: SortNodeEnum,
|
||||
sortNodeParameters: SortNodeEnum.SortNodeParameters) {
|
||||
// Save setting
|
||||
context?.let {
|
||||
PreferencesUtil.saveNodeSort(it, sortNodeEnum, sortNodeParameters)
|
||||
}
|
||||
|
||||
// Tell the adapter to refresh it's list
|
||||
mAdapter?.notifyChangeSort(sortNodeEnum, ascending, groupsBefore)
|
||||
mainGroup?.let { mainGroup ->
|
||||
mAdapter?.rebuildList(mainGroup)
|
||||
}
|
||||
mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
|
||||
rebuildList()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
@@ -228,8 +243,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
R.id.menu_sort -> {
|
||||
context?.let { context ->
|
||||
val sortDialogFragment: SortDialogFragment =
|
||||
if (Database.getInstance().allowRecycleBin
|
||||
&& Database.getInstance().isRecycleBinEnabled) {
|
||||
if (Database.getInstance().isRecycleBinEnabled) {
|
||||
SortDialogFragment.getInstance(
|
||||
PreferencesUtil.getListSort(context),
|
||||
PreferencesUtil.getAscendingSort(context),
|
||||
@@ -251,7 +265,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
}
|
||||
|
||||
fun actionNodesCallback(nodes: List<NodeVersioned>,
|
||||
fun actionNodesCallback(nodes: List<Node>,
|
||||
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
|
||||
|
||||
return object : ActionMode.Callback {
|
||||
@@ -276,7 +290,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
// Open and Edit for a single item
|
||||
if (nodes.size == 1) {
|
||||
// Edition
|
||||
if (readOnly || nodes[0] == database.recycleBin) {
|
||||
if (readOnly
|
||||
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
|
||||
menu?.removeItem(R.id.menu_edit)
|
||||
}
|
||||
} else {
|
||||
@@ -287,7 +302,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
// Copy and Move (not for groups)
|
||||
if (readOnly
|
||||
|| isASearchResult
|
||||
|| nodes.any { it == database.recycleBin }
|
||||
|| nodes.any { it.type == Type.GROUP }) {
|
||||
// TODO COPY For Group
|
||||
menu?.removeItem(R.id.menu_copy)
|
||||
@@ -295,7 +309,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
|
||||
// Deletion
|
||||
if (readOnly || nodes.any { it == database.recycleBin }) {
|
||||
if (readOnly
|
||||
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
|
||||
menu?.removeItem(R.id.menu_delete)
|
||||
}
|
||||
}
|
||||
@@ -354,46 +369,42 @@ 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 { changedNode ->
|
||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
||||
mAdapter?.addNode(newNode)
|
||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||
//mAdapter.updateLastNodeRegister(newNode);
|
||||
mainGroup?.let { mainGroup ->
|
||||
mAdapter?.rebuildList(mainGroup)
|
||||
}
|
||||
}
|
||||
addNode(changedNode)
|
||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE)
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun contains(node: NodeVersioned): Boolean {
|
||||
fun contains(node: Node): Boolean {
|
||||
return mAdapter?.contains(node) ?: false
|
||||
}
|
||||
|
||||
fun addNode(newNode: NodeVersioned) {
|
||||
fun addNode(newNode: Node) {
|
||||
mAdapter?.addNode(newNode)
|
||||
}
|
||||
|
||||
fun addNodes(newNodes: List<NodeVersioned>) {
|
||||
fun addNodes(newNodes: List<Node>) {
|
||||
mAdapter?.addNodes(newNodes)
|
||||
}
|
||||
|
||||
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) {
|
||||
fun updateNode(oldNode: Node, newNode: Node? = null) {
|
||||
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
|
||||
}
|
||||
|
||||
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
|
||||
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||
mAdapter?.updateNodes(oldNodes, newNodes)
|
||||
}
|
||||
|
||||
fun removeNode(pwNode: NodeVersioned) {
|
||||
fun removeNode(pwNode: Node) {
|
||||
mAdapter?.removeNode(pwNode)
|
||||
}
|
||||
|
||||
fun removeNodes(nodes: List<NodeVersioned>) {
|
||||
fun removeNodes(nodes: List<Node>) {
|
||||
mAdapter?.removeNodes(nodes)
|
||||
}
|
||||
|
||||
@@ -409,20 +420,20 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
* Callback listener to redefine to do an action when a node is click
|
||||
*/
|
||||
interface NodeClickListener {
|
||||
fun onNodeClick(node: NodeVersioned)
|
||||
fun onNodeSelected(nodes: List<NodeVersioned>): Boolean
|
||||
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: NodeVersioned): Boolean
|
||||
fun onEditMenuClick(node: NodeVersioned): Boolean
|
||||
fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean
|
||||
fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean
|
||||
fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean
|
||||
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<NodeVersioned>): Boolean
|
||||
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 {
|
||||
@@ -447,7 +458,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,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
|
||||
@@ -23,31 +23,24 @@ import android.app.Activity
|
||||
import android.app.assist.AssistStructure
|
||||
import android.app.backup.BackupManager
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
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.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.*
|
||||
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
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.DuplicateUuidDialog
|
||||
import com.kunzisoft.keepass.activities.dialogs.FingerPrintExplanationDialog
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
@@ -56,11 +49,13 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
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
|
||||
@@ -72,47 +67,53 @@ import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import kotlinx.android.synthetic.main.activity_password.*
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class PasswordActivity : StylishActivity() {
|
||||
open 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
|
||||
private var keyFileSelectionView: KeyFileSelectionView? = null
|
||||
private var confirmButtonView: Button? = null
|
||||
private var checkboxPasswordView: CompoundButton? = null
|
||||
private var checkboxKeyFileView: CompoundButton? = null
|
||||
private var checkboxDefaultDatabaseView: CompoundButton? = null
|
||||
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
|
||||
private var infoContainerView: ViewGroup? = 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 mOpenFileHelper: OpenFileHelper? = null
|
||||
|
||||
private var mPermissionAsked = false
|
||||
private var readOnly: Boolean = false
|
||||
private var mForceReadOnly: Boolean = false
|
||||
set(value) {
|
||||
infoContainerView?.visibility = if (value) {
|
||||
readOnly = true
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
private var progressDialogThread: ProgressDialogThread? = null
|
||||
private var mProgressDialogThread: ProgressDialogThread? = null
|
||||
|
||||
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
|
||||
|
||||
setContentView(R.layout.activity_password)
|
||||
|
||||
toolbar = findViewById(R.id.toolbar)
|
||||
@@ -122,20 +123,26 @@ class PasswordActivity : StylishActivity() {
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
containerView = findViewById(R.id.container)
|
||||
confirmButtonView = findViewById(R.id.pass_ok)
|
||||
confirmButtonView = findViewById(R.id.activity_password_open_button)
|
||||
filenameView = findViewById(R.id.filename)
|
||||
passwordView = findViewById(R.id.password)
|
||||
keyFileView = findViewById(R.id.pass_keyfile)
|
||||
keyFileSelectionView = findViewById(R.id.keyfile_selection)
|
||||
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
||||
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
||||
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
|
||||
advancedUnlockInfoView = findViewById(R.id.fingerprint_info)
|
||||
advancedUnlockInfoView = findViewById(R.id.biometric_info)
|
||||
infoContainerView = findViewById(R.id.activity_password_info_container)
|
||||
|
||||
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||
|
||||
val browseView = findViewById<View>(R.id.open_database_button)
|
||||
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
||||
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
|
||||
keyFileSelectionView?.apply {
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
||||
setOnClickListener(it)
|
||||
setOnLongClickListener(it)
|
||||
}
|
||||
}
|
||||
|
||||
passwordView?.setOnEditorActionListener(onEditorActionListener)
|
||||
passwordView?.addTextChangedListener(object : TextWatcher {
|
||||
@@ -148,26 +155,22 @@ class PasswordActivity : StylishActivity() {
|
||||
checkboxPasswordView?.isChecked = true
|
||||
}
|
||||
})
|
||||
keyFileView?.setOnEditorActionListener(onEditorActionListener)
|
||||
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) {
|
||||
if (editable.toString().isNotEmpty() && checkboxKeyFileView?.isChecked != true)
|
||||
checkboxKeyFileView?.isChecked = true
|
||||
}
|
||||
})
|
||||
|
||||
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
enableOrNotTheConfirmationButton()
|
||||
}
|
||||
|
||||
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
|
||||
// If is a view intent
|
||||
getUriFromIntent(intent)
|
||||
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
|
||||
mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
|
||||
}
|
||||
|
||||
mProgressDialogThread = ProgressDialogThread(this).apply {
|
||||
onActionFinish = { actionTask, result ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_LOAD_TASK -> {
|
||||
// Recheck fingerprint if error
|
||||
// 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
|
||||
@@ -175,6 +178,22 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
if (result.isSuccess) {
|
||||
mDatabaseKeyFileUri = null
|
||||
clearCredentialsViews(true)
|
||||
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
|
||||
@@ -190,20 +209,6 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
databaseUri?.let { databaseFileUri ->
|
||||
// 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)
|
||||
if (resultException is LoadDatabaseDuplicateUuidException)
|
||||
showLoadDatabaseDuplicateUuidMessage {
|
||||
showProgressDialogAndLoadDatabase(
|
||||
databaseFileUri,
|
||||
masterPassword,
|
||||
@@ -213,13 +218,14 @@ class PasswordActivity : StylishActivity() {
|
||||
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()
|
||||
@@ -230,19 +236,59 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUriFromIntent(intent: Intent?) {
|
||||
// If is a view intent
|
||||
val action = intent?.action
|
||||
if (action != null
|
||||
&& action == VIEW_INTENT) {
|
||||
mDatabaseFileUri = intent.data
|
||||
mDatabaseKeyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
|
||||
} else {
|
||||
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
|
||||
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
getUriFromIntent(intent)
|
||||
}
|
||||
|
||||
private fun launchGroupActivity() {
|
||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||
{
|
||||
GroupActivity.launch(this@PasswordActivity, readOnly)
|
||||
GroupActivity.launch(this@PasswordActivity,
|
||||
readOnly)
|
||||
},
|
||||
{
|
||||
GroupActivity.launchForKeyboardSelection(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)
|
||||
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
|
||||
AutofillHelper.checkAutoSearchInfo(this,
|
||||
Database.getInstance(),
|
||||
searchInfo,
|
||||
{ items ->
|
||||
// Response is build
|
||||
AutofillHelper.buildResponse(this, items)
|
||||
finish()
|
||||
},
|
||||
{
|
||||
// Here no search info found
|
||||
GroupActivity.launchForAutofillResult(this@PasswordActivity,
|
||||
assistStructure,
|
||||
null,
|
||||
readOnly)
|
||||
},
|
||||
{
|
||||
// Simply close if database not opened, normally not happened
|
||||
finish()
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -258,62 +304,61 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
if (Database.getInstance().loaded)
|
||||
launchGroupActivity()
|
||||
|
||||
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
|
||||
|
||||
// If the database isn't accessible make sure to clear the password field, if it
|
||||
// was saved in the instance state
|
||||
if (Database.getInstance().loaded) {
|
||||
setEmptyViews()
|
||||
clearCredentialsViews()
|
||||
}
|
||||
|
||||
// For check shutdown
|
||||
super.onResume()
|
||||
|
||||
progressDialogThread?.registerProgressTask()
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
|
||||
initUriFromIntent()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
|
||||
mDatabaseKeyFileUri?.let {
|
||||
outState.putString(KEY_KEYFILE, it.toString())
|
||||
}
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
private fun initUriFromIntent() {
|
||||
|
||||
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)
|
||||
}
|
||||
/*
|
||||
// "canXrite" doesn't work with Google Drive, don't really know why?
|
||||
mForceReadOnly = mDatabaseFileUri?.let {
|
||||
!FileDatabaseInfo(this, it).canWrite
|
||||
} ?: false
|
||||
*/
|
||||
mForceReadOnly = mDatabaseFileUri?.let {
|
||||
!FileDatabaseInfo(this, it).exists
|
||||
} ?: true
|
||||
|
||||
// Post init uri with KeyFile if needed
|
||||
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
|
||||
if (mRememberKeyFile && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
|
||||
// Retrieve KeyFile in a thread
|
||||
databaseUri?.let { databaseUriNotNull ->
|
||||
mDatabaseFileUri?.let { databaseUri ->
|
||||
FileDatabaseHistoryAction.getInstance(applicationContext)
|
||||
.getKeyFileUriByDatabaseUri(databaseUriNotNull) {
|
||||
.getKeyFileUriByDatabaseUri(databaseUri) {
|
||||
onPostInitUri(databaseUri, it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onPostInitUri(databaseUri, keyFileUri)
|
||||
onPostInitUri(mDatabaseFileUri, mDatabaseKeyFileUri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
|
||||
mDatabaseFileUri = databaseFileUri
|
||||
mDatabaseKeyFileUri = keyFileUri
|
||||
|
||||
// Define title
|
||||
databaseFileUri?.let {
|
||||
FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
|
||||
@@ -322,26 +367,18 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
// Define Key File text
|
||||
val keyUriString = keyFileUri?.toString() ?: ""
|
||||
if (keyUriString.isNotEmpty() && mRememberKeyFile) { // Bug KeepassDX #18
|
||||
populateKeyFileTextView(keyUriString)
|
||||
if (mRememberKeyFile) {
|
||||
populateKeyFileTextView(keyFileUri)
|
||||
}
|
||||
|
||||
// Define listeners for default database checkbox and validate button
|
||||
checkboxDefaultDatabaseView?.setOnCheckedChangeListener { _, isChecked ->
|
||||
var newDefaultFileName: Uri? = null
|
||||
var newDefaultFileUri: Uri? = null
|
||||
if (isChecked) {
|
||||
newDefaultFileName = databaseFileUri ?: newDefaultFileName
|
||||
newDefaultFileUri = databaseFileUri ?: newDefaultFileUri
|
||||
}
|
||||
|
||||
prefs?.edit()?.apply {
|
||||
newDefaultFileName?.let {
|
||||
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
|
||||
} ?: kotlin.run {
|
||||
remove(KEY_DEFAULT_DATABASE_PATH)
|
||||
}
|
||||
apply()
|
||||
}
|
||||
PreferencesUtil.saveDefaultDatabasePath(this, newDefaultFileUri)
|
||||
|
||||
val backupManager = BackupManager(this@PasswordActivity)
|
||||
backupManager.dataChanged()
|
||||
@@ -349,7 +386,7 @@ class PasswordActivity : StylishActivity() {
|
||||
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
|
||||
|
||||
// Retrieve settings for default database
|
||||
val defaultFilename = prefs?.getString(KEY_DEFAULT_DATABASE_PATH, "")
|
||||
val defaultFilename = PreferencesUtil.getDefaultDatabasePath(this)
|
||||
if (databaseFileUri != null
|
||||
&& databaseFileUri.path != null && databaseFileUri.path!!.isNotEmpty()
|
||||
&& databaseFileUri == UriUtil.parse(defaultFilename)) {
|
||||
@@ -359,6 +396,8 @@ class PasswordActivity : StylishActivity() {
|
||||
// If Activity is launch with a password and want to open directly
|
||||
val intent = intent
|
||||
val password = intent.getStringExtra(KEY_PASSWORD)
|
||||
// Consume the intent extra password
|
||||
intent.removeExtra(KEY_PASSWORD)
|
||||
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
|
||||
if (password != null) {
|
||||
populatePasswordTextView(password)
|
||||
@@ -366,15 +405,11 @@ 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.isBiometricUnlockEnable(this)) {
|
||||
|
||||
advancedUnlockInfoView?.setOnClickListener {
|
||||
FingerPrintExplanationDialog().show(supportFragmentManager, "fingerPrintExplanationDialog")
|
||||
}
|
||||
|
||||
if (advancedUnlockedManager == null && databaseFileUri != null) {
|
||||
advancedUnlockedManager = AdvancedUnlockedManager(this,
|
||||
databaseFileUri,
|
||||
@@ -396,18 +431,18 @@ class PasswordActivity : StylishActivity() {
|
||||
{ passwordDecrypted ->
|
||||
// Load the database if password is retrieve from biometric
|
||||
passwordDecrypted?.let {
|
||||
// Retrieve from fingerprint
|
||||
// Retrieve from biometric
|
||||
verifyKeyFileCheckboxAndLoadDatabase(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
advancedUnlockedManager?.initBiometric()
|
||||
fingerPrintInit = true
|
||||
advancedUnlockedManager?.checkBiometricAvailability()
|
||||
biometricInitialize = true
|
||||
} else {
|
||||
advancedUnlockedManager?.destroy()
|
||||
}
|
||||
}
|
||||
if (!fingerPrintInit) {
|
||||
if (!biometricInitialize) {
|
||||
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||
}
|
||||
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||
@@ -428,10 +463,9 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setEmptyViews() {
|
||||
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
|
||||
populatePasswordTextView(null)
|
||||
// Bug KeepassDX #18
|
||||
if (!mRememberKeyFile) {
|
||||
if (clearKeyFile) {
|
||||
populateKeyFileTextView(null)
|
||||
}
|
||||
}
|
||||
@@ -448,24 +482,20 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateKeyFileTextView(text: String?) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
keyFileView?.setText("")
|
||||
private fun populateKeyFileTextView(uri: Uri?) {
|
||||
if (uri == null || uri.toString().isEmpty()) {
|
||||
keyFileSelectionView?.uri = null
|
||||
if (checkboxKeyFileView?.isChecked == true)
|
||||
checkboxKeyFileView?.isChecked = false
|
||||
} else {
|
||||
keyFileView?.setText(text)
|
||||
keyFileSelectionView?.uri = uri
|
||||
if (checkboxKeyFileView?.isChecked != true)
|
||||
checkboxKeyFileView?.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
advancedUnlockedManager?.pause()
|
||||
}
|
||||
|
||||
progressDialogThread?.unregisterProgressTask()
|
||||
mProgressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
@@ -479,7 +509,7 @@ class PasswordActivity : StylishActivity() {
|
||||
|
||||
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||
val password: String? = passwordView?.text?.toString()
|
||||
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
||||
val keyFile: Uri? = keyFileSelectionView?.uri
|
||||
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
|
||||
}
|
||||
|
||||
@@ -492,7 +522,7 @@ class PasswordActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
|
||||
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
||||
val keyFile: Uri? = keyFileSelectionView?.uri
|
||||
verifyKeyFileCheckbox(keyFile)
|
||||
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
|
||||
}
|
||||
@@ -501,18 +531,13 @@ class PasswordActivity : StylishActivity() {
|
||||
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
||||
}
|
||||
|
||||
private fun removePassword() {
|
||||
passwordView?.setText("")
|
||||
checkboxPasswordView?.isChecked = false
|
||||
}
|
||||
|
||||
private fun loadDatabase(databaseFileUri: Uri?,
|
||||
password: String?,
|
||||
keyFileUri: Uri?,
|
||||
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||
|
||||
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
|
||||
removePassword()
|
||||
clearCredentialsViews()
|
||||
}
|
||||
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
@@ -533,7 +558,7 @@ class PasswordActivity : StylishActivity() {
|
||||
readOnly: Boolean,
|
||||
cipherDatabaseEntity: CipherDatabaseEntity?,
|
||||
fixDuplicateUUID: Boolean) {
|
||||
progressDialogThread?.startDatabaseLoad(
|
||||
mProgressDialogThread?.startDatabaseLoad(
|
||||
databaseUri,
|
||||
password,
|
||||
keyFile,
|
||||
@@ -549,69 +574,116 @@ class PasswordActivity : StylishActivity() {
|
||||
}.show(supportFragmentManager, "duplicateUUIDDialog")
|
||||
}
|
||||
|
||||
// To fix multiple view education
|
||||
private var performedEductionInProgress = false
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val inflater = menuInflater
|
||||
// Read menu
|
||||
inflater.inflate(R.menu.open_file, menu)
|
||||
|
||||
if (mForceReadOnly) {
|
||||
menu.removeItem(R.id.menu_open_file_read_mode_key)
|
||||
} else {
|
||||
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
|
||||
}
|
||||
|
||||
MenuUtil.defaultMenuInflater(inflater, menu)
|
||||
|
||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Fingerprint menu
|
||||
// biometric menu
|
||||
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
|
||||
}
|
||||
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
if (!performedEductionInProgress) {
|
||||
performedEductionInProgress = true
|
||||
// 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)? = null) {
|
||||
if (!performedEductionInProgress) {
|
||||
performedEductionInProgress = true
|
||||
// Show education views
|
||||
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
|
||||
menu: Menu) {
|
||||
val educationContainerView = containerView
|
||||
val unlockEducationPerformed = educationContainerView != null
|
||||
menu: Menu,
|
||||
onEducationFinished: (()-> Unit)? = null) {
|
||||
val educationToolbar = toolbar
|
||||
val unlockEducationPerformed = educationToolbar != null
|
||||
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
|
||||
educationContainerView,
|
||||
educationToolbar,
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
})
|
||||
if (!unlockEducationPerformed) {
|
||||
val educationToolbar = toolbar
|
||||
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)
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
})
|
||||
|
||||
if (!readOnlyEducationPerformed) {
|
||||
|
||||
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
|
||||
// fingerprintEducationPerformed
|
||||
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.checkAndPerformedFingerprintEducation(advancedUnlockInfoView?.unlockIconImageView!!)
|
||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||
})
|
||||
|
||||
if (!biometricEducationPerformed) {
|
||||
onEducationFinished?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,7 +706,7 @@ class PasswordActivity : StylishActivity() {
|
||||
readOnly = !readOnly
|
||||
changeOpenFileReadIcon(item)
|
||||
}
|
||||
R.id.menu_fingerprint_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
advancedUnlockedManager?.deleteEntryKey()
|
||||
}
|
||||
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||
@@ -659,7 +731,8 @@ class PasswordActivity : StylishActivity() {
|
||||
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
populateKeyFileTextView(uri.toString())
|
||||
mDatabaseKeyFileUri = uri
|
||||
populateKeyFileTextView(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -667,7 +740,7 @@ class PasswordActivity : StylishActivity() {
|
||||
// this block if not a key file response
|
||||
when (resultCode) {
|
||||
LockingActivity.RESULT_EXIT_LOCK, Activity.RESULT_CANCELED -> {
|
||||
setEmptyViews()
|
||||
clearCredentialsViews()
|
||||
Database.getInstance().closeAndClear(applicationContext.filesDir)
|
||||
}
|
||||
}
|
||||
@@ -678,14 +751,14 @@ class PasswordActivity : StylishActivity() {
|
||||
|
||||
private val TAG = PasswordActivity::class.java.name
|
||||
|
||||
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 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) {
|
||||
@@ -740,13 +813,15 @@ class PasswordActivity : StylishActivity() {
|
||||
activity: Activity,
|
||||
databaseFile: Uri,
|
||||
keyFile: Uri?,
|
||||
assistStructure: AssistStructure?) {
|
||||
assistStructure: AssistStructure?,
|
||||
searchInfo: SearchInfo?) {
|
||||
if (assistStructure != null) {
|
||||
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||
AutofillHelper.startActivityForAutofillResult(
|
||||
activity,
|
||||
intent,
|
||||
assistStructure)
|
||||
assistStructure,
|
||||
searchInfo)
|
||||
}
|
||||
} else {
|
||||
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
|
||||
@@ -35,7 +35,7 @@ import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||
|
||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
|
||||
@@ -51,9 +51,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
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 keyFileSelectionView: KeyFileSelectionView? = null
|
||||
|
||||
private var mListener: AssignPasswordDialogListener? = null
|
||||
|
||||
@@ -69,16 +68,6 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
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?,
|
||||
keyFileChecked: Boolean, keyFile: Uri?)
|
||||
@@ -121,13 +110,14 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
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)
|
||||
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
|
||||
|
||||
mOpenFileHelper = OpenFileHelper(this)
|
||||
rootView?.findViewById<View>(R.id.open_database_button)?.setOnClickListener { view ->
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
||||
keyFileSelectionView?.apply {
|
||||
setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
@@ -176,14 +166,12 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
|
||||
// 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 {
|
||||
@@ -216,11 +204,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
if (keyFileCheckBox != null
|
||||
&& keyFileCheckBox!!.isChecked) {
|
||||
|
||||
UriUtil.parse(keyFileView?.text?.toString())?.let { uri ->
|
||||
keyFileSelectionView?.uri?.let { uri ->
|
||||
mKeyFile = uri
|
||||
} ?: run {
|
||||
error = true
|
||||
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile)
|
||||
keyFileSelectionView?.error = getString(R.string.error_nokeyfile)
|
||||
}
|
||||
}
|
||||
return error
|
||||
@@ -265,8 +253,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
) { uri ->
|
||||
uri?.let { pathUri ->
|
||||
keyFileCheckBox?.isChecked = true
|
||||
keyFileView?.text = pathUri.toString()
|
||||
|
||||
keyFileSelectionView?.uri = pathUri
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
|
||||
class DatePickerFragment : DialogFragment() {
|
||||
|
||||
private var mDefaultYear: Int = 2000
|
||||
private var mDefaultMonth: Int = 1
|
||||
private var mDefaultDay: Int = 1
|
||||
|
||||
private var mListener: DatePickerDialog.OnDateSetListener? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
mListener = context as DatePickerDialog.OnDateSetListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
return context?.let {
|
||||
arguments?.apply {
|
||||
if (containsKey(DEFAULT_YEAR_BUNDLE_KEY))
|
||||
mDefaultYear = getInt(DEFAULT_YEAR_BUNDLE_KEY)
|
||||
if (containsKey(DEFAULT_MONTH_BUNDLE_KEY))
|
||||
mDefaultMonth = getInt(DEFAULT_MONTH_BUNDLE_KEY)
|
||||
if (containsKey(DEFAULT_DAY_BUNDLE_KEY))
|
||||
mDefaultDay = getInt(DEFAULT_DAY_BUNDLE_KEY)
|
||||
}
|
||||
|
||||
DatePickerDialog(it, mListener, mDefaultYear, mDefaultMonth, mDefaultDay)
|
||||
} ?: super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val DEFAULT_YEAR_BUNDLE_KEY = "DEFAULT_YEAR_BUNDLE_KEY"
|
||||
private const val DEFAULT_MONTH_BUNDLE_KEY = "DEFAULT_MONTH_BUNDLE_KEY"
|
||||
private const val DEFAULT_DAY_BUNDLE_KEY = "DEFAULT_DAY_BUNDLE_KEY"
|
||||
|
||||
fun getInstance(defaultYear: Int,
|
||||
defaultMonth: Int,
|
||||
defaultDay: Int): DatePickerFragment {
|
||||
return DatePickerFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putInt(DEFAULT_YEAR_BUNDLE_KEY, defaultYear)
|
||||
putInt(DEFAULT_MONTH_BUNDLE_KEY, defaultMonth)
|
||||
putInt(DEFAULT_DAY_BUNDLE_KEY, defaultDay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,73 +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.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector
|
||||
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class FingerPrintExplanationDialog : DialogFragment() {
|
||||
|
||||
private var fingerPrintAnimatedVector: FingerPrintAnimatedVector? = null
|
||||
|
||||
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_fingerprint_explanation, null)
|
||||
|
||||
rootView.findViewById<View>(R.id.fingerprint_setting_link_text).setOnClickListener {
|
||||
startActivity(Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
|
||||
rootView.findViewById<View>(R.id.auto_open_biometric_prompt_button).setOnClickListener {
|
||||
startActivity(Intent(activity, SettingsAdvancedUnlockActivity::class.java))
|
||||
}
|
||||
|
||||
fingerPrintAnimatedVector = FingerPrintAnimatedVector(activity,
|
||||
rootView.findViewById(R.id.biometric_image))
|
||||
|
||||
builder.setView(rootView)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
fingerPrintAnimatedVector?.startScan()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
fingerPrintAnimatedVector?.stopScan()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -72,7 +72,7 @@ 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()
|
||||
}
|
||||
@@ -112,7 +112,7 @@ class IconPickerDialogFragment : DialogFragment() {
|
||||
// Retrieve the textColor to tint the icon
|
||||
val ta = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
|
||||
ImageViewCompat.setImageTintList(iconImageView, ColorStateList.valueOf(ta.getColor(0, Color.BLACK)))
|
||||
ta?.recycle()
|
||||
ta.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,65 +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 androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
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_device_setting_button)
|
||||
.setOnClickListener { launchActivateKeyboardSetting() }
|
||||
|
||||
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
|
||||
if (BuildConfig.CLOSED_STORE) {
|
||||
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
|
||||
} else {
|
||||
containerKeyboardSwitcher.setOnClickListener { UriUtil.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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,56 +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.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
class ReadOnlyDialog : DialogFragment() {
|
||||
|
||||
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)
|
||||
|
||||
var warning = getString(R.string.read_only_warning)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
|
||||
}
|
||||
builder.setMessage(warning)
|
||||
|
||||
builder.setPositiveButton(getString(android.R.string.ok)) { _, _ -> dismiss() }
|
||||
builder.setNegativeButton(getString(R.string.beta_dontask)) { _, _ ->
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val edit = prefs.edit()
|
||||
edit.putBoolean(getString(R.string.show_read_only_warning), false)
|
||||
edit.apply()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
// Create the AlertDialog object and return it
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(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
|
||||
@@ -29,7 +29,7 @@ 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() {
|
||||
|
||||
@@ -82,7 +82,9 @@ class SortDialogFragment : DialogFragment() {
|
||||
builder.setView(rootView)
|
||||
// Add action buttons
|
||||
.setPositiveButton(android.R.string.ok
|
||||
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) }
|
||||
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum,
|
||||
SortNodeEnum.SortNodeParameters(mAscending, mGroupsBefore, mRecycleBinBottom))
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
|
||||
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
|
||||
@@ -150,10 +152,7 @@ class SortDialogFragment : DialogFragment() {
|
||||
}
|
||||
|
||||
interface SortSelectionListener {
|
||||
fun onSortSelected(sortNodeEnum: SortNodeEnum,
|
||||
ascending: Boolean,
|
||||
groupsBefore: Boolean,
|
||||
recycleBinBottom: Boolean)
|
||||
fun onSortSelected(sortNodeEnum: SortNodeEnum, sortNodeParameters: SortNodeEnum.SortNodeParameters)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.app.Dialog
|
||||
import android.app.TimePickerDialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateFormat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
|
||||
class TimePickerFragment : DialogFragment() {
|
||||
|
||||
private var defaultHour: Int = 0
|
||||
private var defaultMinute: Int = 0
|
||||
|
||||
private var mListener: TimePickerDialog.OnTimeSetListener? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
mListener = context as TimePickerDialog.OnTimeSetListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
return context?.let {
|
||||
arguments?.apply {
|
||||
if (containsKey(DEFAULT_HOUR_BUNDLE_KEY))
|
||||
defaultHour = getInt(DEFAULT_HOUR_BUNDLE_KEY)
|
||||
if (containsKey(DEFAULT_MINUTE_BUNDLE_KEY))
|
||||
defaultMinute = getInt(DEFAULT_MINUTE_BUNDLE_KEY)
|
||||
}
|
||||
|
||||
TimePickerDialog(it, mListener, defaultHour, defaultMinute, DateFormat.is24HourFormat(activity))
|
||||
} ?: super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val DEFAULT_HOUR_BUNDLE_KEY = "DEFAULT_HOUR_BUNDLE_KEY"
|
||||
private const val DEFAULT_MINUTE_BUNDLE_KEY = "DEFAULT_MINUTE_BUNDLE_KEY"
|
||||
|
||||
fun getInstance(defaultHour: Int,
|
||||
defaultMinute: Int): TimePickerFragment {
|
||||
return TimePickerFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putInt(DEFAULT_HOUR_BUNDLE_KEY, defaultHour)
|
||||
putInt(DEFAULT_MINUTE_BUNDLE_KEY, defaultMinute)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.app.assist.AssistStructure
|
||||
|
||||
@@ -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.helpers
|
||||
@@ -52,15 +52,23 @@ class OpenFileHelper {
|
||||
this.fragment = context
|
||||
}
|
||||
|
||||
inner class OpenFileOnClickViewListener : View.OnClickListener {
|
||||
inner class OpenFileOnClickViewListener : View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
override fun onClick(v: View) {
|
||||
private fun onAbstractClick(longClick: Boolean = false) {
|
||||
try {
|
||||
if (longClick) {
|
||||
try {
|
||||
openActivityWithActionGetContent()
|
||||
} catch (e: Exception) {
|
||||
openActivityWithActionOpenDocument()
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
openActivityWithActionOpenDocument()
|
||||
} catch(e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
openActivityWithActionGetContent()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Enable to start the file picker activity", e)
|
||||
// Open browser dialog
|
||||
@@ -68,6 +76,15 @@ class OpenFileHelper {
|
||||
showBrowserDialog()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
onAbstractClick()
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
onAbstractClick(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
@@ -75,9 +92,10 @@ class OpenFileHelper {
|
||||
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
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(intentOpenDocument, OPEN_DOC)
|
||||
@@ -85,10 +103,15 @@ class OpenFileHelper {
|
||||
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun openActivityWithActionGetContent() {
|
||||
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(intentGetContent, GET_CONTENT)
|
||||
|
||||
@@ -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,30 +1,26 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
@@ -32,12 +28,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.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
|
||||
abstract class LockingActivity : StylishActivity() {
|
||||
|
||||
@@ -62,6 +57,10 @@ abstract class LockingActivity : StylishActivity() {
|
||||
return field || mSelectionMode
|
||||
}
|
||||
protected var mSelectionMode: Boolean = false
|
||||
protected var mAutoSaveEnable: Boolean = true
|
||||
|
||||
var mProgressDialogThread: ProgressDialogThread? = null
|
||||
private set
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -75,16 +74,16 @@ abstract class LockingActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
if (mTimeoutEnable) {
|
||||
mLockReceiver = LockReceiver()
|
||||
val intentFilter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_SCREEN_OFF)
|
||||
addAction(LOCK_ACTION)
|
||||
mLockReceiver = LockReceiver {
|
||||
lockAndExit()
|
||||
}
|
||||
registerReceiver(mLockReceiver, intentFilter)
|
||||
registerLockReceiver(mLockReceiver)
|
||||
}
|
||||
|
||||
mExitLock = false
|
||||
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
|
||||
|
||||
mProgressDialogThread = ProgressDialogThread(this)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
@@ -100,8 +99,13 @@ abstract class LockingActivity : StylishActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
|
||||
// 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
|
||||
@@ -118,8 +122,6 @@ abstract class LockingActivity : StylishActivity() {
|
||||
if (!mExitLock)
|
||||
TimeoutHelper.recordTime(this)
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -129,6 +131,8 @@ abstract class LockingActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mProgressDialogThread?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
|
||||
if (mTimeoutEnable) {
|
||||
@@ -138,29 +142,12 @@ abstract class LockingActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterLockReceiver(mLockReceiver)
|
||||
super.onDestroy()
|
||||
if (mLockReceiver != null)
|
||||
unregisterReceiver(mLockReceiver)
|
||||
}
|
||||
|
||||
inner class LockReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// If allowed, lock and exit
|
||||
if (!TimeoutHelper.temporarilyDisableTimeout) {
|
||||
intent.action?.let {
|
||||
when (it) {
|
||||
Intent.ACTION_SCREEN_OFF -> if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this@LockingActivity)) {
|
||||
lockAndExit()
|
||||
}
|
||||
LOCK_ACTION -> lockAndExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun lockAndExit() {
|
||||
sendBroadcast(Intent(LOCK_ACTION))
|
||||
lock()
|
||||
}
|
||||
|
||||
@@ -195,17 +182,8 @@ abstract class LockingActivity : StylishActivity() {
|
||||
}
|
||||
|
||||
fun Activity.lock() {
|
||||
// Stop the Magikeyboard service
|
||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||
MagikIME.removeEntry(this)
|
||||
closeDatabase()
|
||||
|
||||
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
|
||||
" after inactivity or manual lock")
|
||||
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
|
||||
cancelAll()
|
||||
}
|
||||
// Clear data
|
||||
Database.getInstance().closeAndClear(applicationContext.filesDir)
|
||||
// Add onActivityForResult response
|
||||
setResult(LockingActivity.RESULT_EXIT_LOCK)
|
||||
finish()
|
||||
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
|
||||
@@ -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 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
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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
|
||||
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
|
||||
|
||||
private val mDatabase = Database.getInstance()
|
||||
|
||||
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 (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
|
||||
|| 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)
|
||||
}
|
||||
}
|
||||
@@ -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.adapters
|
||||
|
||||
import android.content.Context
|
||||
@@ -7,13 +26,13 @@ import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||
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<EntryVersioned> = ArrayList()
|
||||
var onItemClickListener: ((item: EntryVersioned, position: Int)->Unit)? = null
|
||||
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))
|
||||
|
||||
@@ -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.adapters
|
||||
|
||||
import android.content.Context
|
||||
@@ -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,25 +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.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.TypedValue
|
||||
@@ -82,15 +84,27 @@ class FileDatabaseHistoryAdapter(private val context: Context)
|
||||
// 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()
|
||||
if (fileDatabaseInfo.exists) {
|
||||
holder.fileInformation.clearColorFilter()
|
||||
} else {
|
||||
holder.fileInformation.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)
|
||||
}
|
||||
|
||||
View.VISIBLE
|
||||
} else
|
||||
View.GONE
|
||||
// Modification
|
||||
fileDatabaseInfo.getModificationString()?.let {
|
||||
holder.fileModification.text = it
|
||||
holder.fileModification.visibility = View.VISIBLE
|
||||
} ?: run {
|
||||
holder.fileModification.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Size
|
||||
fileDatabaseInfo.getSizeString()?.let {
|
||||
holder.fileSize.text = it
|
||||
holder.fileSize.visibility = View.VISIBLE
|
||||
} ?: run {
|
||||
holder.fileSize.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Click on information
|
||||
val isExpanded = position == mExpandedPosition
|
||||
@@ -142,6 +156,10 @@ class FileDatabaseHistoryAdapter(private val context: Context)
|
||||
return listDatabaseFiles.size
|
||||
}
|
||||
|
||||
fun clearDatabaseFileHistoryList() {
|
||||
listDatabaseFiles.clear()
|
||||
}
|
||||
|
||||
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
|
||||
listDatabaseFiles.clear()
|
||||
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
|
||||
@@ -178,7 +196,6 @@ class FileDatabaseHistoryAdapter(private val context: Context)
|
||||
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,71 +1,74 @@
|
||||
/*
|
||||
* 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.graphics.Paint
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SortedList
|
||||
import androidx.recyclerview.widget.SortedListAdapterCallback
|
||||
import com.kunzisoft.keepass.R
|
||||
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.NodeVersionedInterface
|
||||
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.setTextSize
|
||||
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)
|
||||
class NodeAdapter (private val context: Context)
|
||||
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
|
||||
|
||||
private val nodeSortedList: SortedList<NodeVersioned>
|
||||
private var nodeComparator: Comparator<NodeVersionedInterface<Group>>? = null
|
||||
private val nodeSortedListCallback: NodeSortedListCallback
|
||||
private val nodeSortedList: SortedList<Node>
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
|
||||
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 prefSizeMultiplier: Float = 0F
|
||||
private var subtextDefaultDimension: Float = 0F
|
||||
private var infoTextDefaultDimension: Float = 0F
|
||||
private var numberChildrenTextDefaultDimension: Float = 0F
|
||||
private var iconDefaultDimension: Float = 0F
|
||||
|
||||
private var showUserNames: Boolean = true
|
||||
private var showNumberEntries: Boolean = true
|
||||
private var entryFilters = arrayOf<Group.ChildFilter>()
|
||||
|
||||
private var actionNodesList = LinkedList<NodeVersioned>()
|
||||
private var actionNodesList = LinkedList<Node>()
|
||||
private var nodeClickCallback: NodeClickCallback? = null
|
||||
|
||||
private val mDatabase: Database
|
||||
@@ -81,23 +84,15 @@ class NodeAdapter
|
||||
get() = nodeSortedList.size() <= 0
|
||||
|
||||
init {
|
||||
this.infoTextDefaultDimension = context.resources.getDimension(R.dimen.list_medium_size_default)
|
||||
this.subtextDefaultDimension = context.resources.getDimension(R.dimen.list_small_size_default)
|
||||
this.numberChildrenTextDefaultDimension = context.resources.getDimension(R.dimen.list_tiny_size_default)
|
||||
this.iconDefaultDimension = context.resources.getDimension(R.dimen.list_icon_size_default)
|
||||
|
||||
assignPreferences()
|
||||
|
||||
this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback<NodeVersioned>(this) {
|
||||
override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int {
|
||||
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean {
|
||||
return oldItem.type == newItem.type
|
||||
&& oldItem.title == newItem.title
|
||||
&& oldItem.icon == newItem.icon
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(item1: NodeVersioned, item2: NodeVersioned): Boolean {
|
||||
return item1 == item2
|
||||
}
|
||||
})
|
||||
this.nodeSortedListCallback = NodeSortedListCallback()
|
||||
this.nodeSortedList = SortedList(Node::class.java, nodeSortedListCallback)
|
||||
|
||||
// Database
|
||||
this.mDatabase = Database.getInstance()
|
||||
@@ -112,20 +107,23 @@ class NodeAdapter
|
||||
taTextColor.recycle()
|
||||
}
|
||||
|
||||
private fun assignPreferences() {
|
||||
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
|
||||
fun assignPreferences() {
|
||||
this.prefSizeMultiplier = PreferencesUtil.getListTextSize(context)
|
||||
|
||||
notifyChangeSort(
|
||||
PreferencesUtil.getListSort(context),
|
||||
SortNodeEnum.SortNodeParameters(
|
||||
PreferencesUtil.getAscendingSort(context),
|
||||
PreferencesUtil.getGroupsBeforeSort(context),
|
||||
PreferencesUtil.getRecycleBinBottomSort(context)
|
||||
)
|
||||
)
|
||||
|
||||
this.listSort = PreferencesUtil.getListSort(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 }
|
||||
}
|
||||
@@ -133,19 +131,29 @@ class NodeAdapter
|
||||
/**
|
||||
* Rebuild the list by clear and build children from the group
|
||||
*/
|
||||
fun rebuildList(group: GroupVersioned) {
|
||||
this.nodeSortedList.clear()
|
||||
fun rebuildList(group: Group) {
|
||||
assignPreferences()
|
||||
try {
|
||||
this.nodeSortedList.addAll(group.getChildren())
|
||||
} 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()
|
||||
nodeSortedList.replaceAll(group.getFilteredChildren(*entryFilters)
|
||||
)
|
||||
}
|
||||
|
||||
fun contains(node: NodeVersioned): Boolean {
|
||||
private inner class NodeSortedListCallback: SortedListAdapterCallback<Node>(this) {
|
||||
override fun compare(item1: Node, item2: Node): Int {
|
||||
return nodeComparator!!.compare(item1, item2)
|
||||
}
|
||||
|
||||
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: Node, item2: Node): Boolean {
|
||||
return item1 == item2
|
||||
}
|
||||
}
|
||||
|
||||
fun contains(node: Node): Boolean {
|
||||
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
|
||||
}
|
||||
|
||||
@@ -153,7 +161,7 @@ class NodeAdapter
|
||||
* Add a node to the list
|
||||
* @param node Node to add
|
||||
*/
|
||||
fun addNode(node: NodeVersioned) {
|
||||
fun addNode(node: Node) {
|
||||
nodeSortedList.add(node)
|
||||
}
|
||||
|
||||
@@ -161,7 +169,7 @@ class NodeAdapter
|
||||
* Add nodes to the list
|
||||
* @param nodes Nodes to add
|
||||
*/
|
||||
fun addNodes(nodes: List<NodeVersioned>) {
|
||||
fun addNodes(nodes: List<Node>) {
|
||||
nodeSortedList.addAll(nodes)
|
||||
}
|
||||
|
||||
@@ -169,7 +177,7 @@ class NodeAdapter
|
||||
* Remove a node in the list
|
||||
* @param node Node to delete
|
||||
*/
|
||||
fun removeNode(node: NodeVersioned) {
|
||||
fun removeNode(node: Node) {
|
||||
nodeSortedList.remove(node)
|
||||
}
|
||||
|
||||
@@ -177,7 +185,7 @@ class NodeAdapter
|
||||
* Remove nodes in the list
|
||||
* @param nodes Nodes to delete
|
||||
*/
|
||||
fun removeNodes(nodes: List<NodeVersioned>) {
|
||||
fun removeNodes(nodes: List<Node>) {
|
||||
nodes.forEach { node ->
|
||||
nodeSortedList.remove(node)
|
||||
}
|
||||
@@ -209,7 +217,7 @@ class NodeAdapter
|
||||
* @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)
|
||||
@@ -221,7 +229,7 @@ class NodeAdapter
|
||||
* @param oldNodes Nodes before the update
|
||||
* @param newNodes Node after the update
|
||||
*/
|
||||
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
|
||||
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||
nodeSortedList.beginBatchedUpdates()
|
||||
oldNodes.forEach { oldNode ->
|
||||
nodeSortedList.remove(oldNode)
|
||||
@@ -230,11 +238,11 @@ class NodeAdapter
|
||||
nodeSortedList.endBatchedUpdates()
|
||||
}
|
||||
|
||||
fun notifyNodeChanged(node: NodeVersioned) {
|
||||
fun notifyNodeChanged(node: Node) {
|
||||
notifyItemChanged(nodeSortedList.indexOf(node))
|
||||
}
|
||||
|
||||
fun setActionNodes(actionNodes: List<NodeVersioned>) {
|
||||
fun setActionNodes(actionNodes: List<Node>) {
|
||||
this.actionNodesList.apply {
|
||||
clear()
|
||||
addAll(actionNodes)
|
||||
@@ -253,10 +261,9 @@ class NodeAdapter
|
||||
/**
|
||||
* Notify a change sort of the list
|
||||
*/
|
||||
fun notifyChangeSort(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean) {
|
||||
this.listSort = sortNodeEnum
|
||||
this.ascendingSort = ascending
|
||||
this.groupsBeforeSort = groupsBefore
|
||||
fun notifyChangeSort(sortNodeEnum: SortNodeEnum,
|
||||
sortNodeParameters: SortNodeEnum.SortNodeParameters) {
|
||||
this.nodeComparator = sortNodeEnum.getNodeComparator(sortNodeParameters)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
@@ -283,19 +290,57 @@ class NodeAdapter
|
||||
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
|
||||
// Relative size of the icon
|
||||
layoutParams?.apply {
|
||||
height = iconSize.toInt()
|
||||
width = iconSize.toInt()
|
||||
height = (iconDefaultDimension * prefSizeMultiplier).toInt()
|
||||
width = (iconDefaultDimension * prefSizeMultiplier).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
// Assign text
|
||||
holder.text.apply {
|
||||
text = subNode.title
|
||||
setTextSize(textSizeUnit, infoTextSize)
|
||||
paintFlags = if (subNode.isCurrentlyExpires)
|
||||
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||
else
|
||||
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
|
||||
setTextSize(textSizeUnit, infoTextDefaultDimension, prefSizeMultiplier)
|
||||
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()
|
||||
holder.subText.apply {
|
||||
val username = entry.username
|
||||
if (showUserNames && username.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
text = username
|
||||
setTextSize(textSizeUnit, subtextDefaultDimension, prefSizeMultiplier)
|
||||
}
|
||||
}
|
||||
|
||||
mDatabase.stopManageEntry(entry)
|
||||
}
|
||||
|
||||
// Add number of entries in groups
|
||||
if (subNode.type == Type.GROUP) {
|
||||
if (showNumberEntries) {
|
||||
holder.numberChildren?.apply {
|
||||
text = (subNode as Group)
|
||||
.getNumberOfChildEntries(*entryFilters)
|
||||
.toString()
|
||||
setTextSize(textSizeUnit, numberChildrenTextDefaultDimension, prefSizeMultiplier)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
holder.numberChildren?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Assign click
|
||||
holder.container.setOnClickListener {
|
||||
nodeClickCallback?.onNodeClick(subNode)
|
||||
@@ -305,45 +350,6 @@ class NodeAdapter
|
||||
}
|
||||
|
||||
holder.container.isSelected = actionNodesList.contains(subNode)
|
||||
|
||||
// Add subText with username
|
||||
holder.subText.apply {
|
||||
text = ""
|
||||
paintFlags = if (subNode.isCurrentlyExpires)
|
||||
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||
else
|
||||
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
|
||||
visibility = View.GONE
|
||||
if (subNode.type == Type.ENTRY) {
|
||||
val entry = subNode as EntryVersioned
|
||||
|
||||
mDatabase.startManageEntry(entry)
|
||||
|
||||
holder.text.text = entry.getVisualTitle()
|
||||
|
||||
val username = entry.username
|
||||
if (showUserNames && username.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
text = username
|
||||
setTextSize(textSizeUnit, subtextSize)
|
||||
}
|
||||
|
||||
mDatabase.stopManageEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Add number of entries in groups
|
||||
if (subNode.type == Type.GROUP) {
|
||||
if (showNumberEntries) {
|
||||
holder.numberChildren?.apply {
|
||||
text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
|
||||
setTextSize(textSizeUnit, numberChildrenTextSize)
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
holder.numberChildren?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@@ -361,8 +367,8 @@ class NodeAdapter
|
||||
* Callback listener to redefine to do an action when a node is click
|
||||
*/
|
||||
interface NodeClickCallback {
|
||||
fun onNodeClick(node: NodeVersioned)
|
||||
fun onNodeLongClick(node: NodeVersioned): Boolean
|
||||
fun onNodeClick(node: Node)
|
||||
fun onNodeLongClick(node: Node): Boolean
|
||||
}
|
||||
|
||||
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
@@ -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,26 +22,30 @@ package com.kunzisoft.keepass.adapters
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.graphics.Color
|
||||
import androidx.cursoradapter.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.cursor.EntryCursorKDB
|
||||
import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX
|
||||
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.database.element.Group
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||
import com.kunzisoft.keepass.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)
|
||||
class SearchEntryCursorAdapter(private val context: Context,
|
||||
private val database: Database)
|
||||
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
|
||||
|
||||
private val cursorInflater: LayoutInflater = context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
private val cursorInflater: LayoutInflater? = context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
private var displayUsername: Boolean = false
|
||||
private val iconColor: Int
|
||||
|
||||
@@ -60,7 +64,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
||||
|
||||
override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
|
||||
|
||||
val view = cursorInflater.inflate(R.layout.item_search_entry, parent, false)
|
||||
val view = cursorInflater!!.inflate(R.layout.item_search_entry, parent, false)
|
||||
val viewHolder = ViewHolder()
|
||||
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon)
|
||||
viewHolder.textViewTitle = view.findViewById(R.id.entry_text)
|
||||
@@ -71,36 +75,84 @@ 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))
|
||||
|
||||
getEntryFrom(cursor)?.let { currentEntry ->
|
||||
val viewHolder = view.tag as ViewHolder
|
||||
|
||||
// Assign image
|
||||
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
|
||||
viewHolder.imageViewIcon?.assignDatabaseIcon(
|
||||
database.drawFactory,
|
||||
currentEntry.icon,
|
||||
iconColor)
|
||||
|
||||
// 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 = ""
|
||||
viewHolder.textViewTitle?.apply {
|
||||
text = currentEntry.getVisualTitle()
|
||||
strikeOut(currentEntry.isCurrentlyExpires)
|
||||
}
|
||||
|
||||
// Assign subtitle
|
||||
viewHolder.textViewSubTitle?.apply {
|
||||
val entryUsername = currentEntry.username
|
||||
text = if (displayUsername && entryUsername.isNotEmpty()) {
|
||||
String.format("(%s)", entryUsername)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
strikeOut(currentEntry.isCurrentlyExpires)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEntryFrom(cursor: Cursor): Entry? {
|
||||
return database.createEntry()?.apply {
|
||||
database.startManageEntry(this)
|
||||
entryKDB?.let { entryKDB ->
|
||||
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconFactory)
|
||||
}
|
||||
entryKDBX?.let { entryKDBX ->
|
||||
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconFactory)
|
||||
}
|
||||
database.stopManageEntry(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
|
||||
return searchEntries(context, constraint.toString())
|
||||
}
|
||||
|
||||
private fun searchEntries(context: Context, query: String): Cursor? {
|
||||
var cursorKDB: EntryCursorKDB? = null
|
||||
var cursorKDBX: EntryCursorKDBX? = null
|
||||
|
||||
if (database.type == DatabaseKDB.TYPE)
|
||||
cursorKDB = EntryCursorKDB()
|
||||
if (database.type == DatabaseKDBX.TYPE)
|
||||
cursorKDBX = EntryCursorKDBX()
|
||||
|
||||
val searchGroup = database.createVirtualGroupFromSearch(query, SearchHelper.MAX_SEARCH_ENTRY)
|
||||
if (searchGroup != null) {
|
||||
// Search in hide entries but not meta-stream
|
||||
for (entry in searchGroup.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) {
|
||||
entry.entryKDB?.let {
|
||||
cursorKDB?.addEntry(it)
|
||||
}
|
||||
entry.entryKDBX?.let {
|
||||
cursorKDBX?.addEntry(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cursorKDB ?: cursorKDBX
|
||||
}
|
||||
|
||||
fun getEntryFromPosition(position: Int): Entry? {
|
||||
var pwEntry: Entry? = null
|
||||
|
||||
val cursor = this.cursor
|
||||
if (cursor.moveToFirst() && cursor.move(position)) {
|
||||
pwEntry = getEntryFrom(cursor)
|
||||
}
|
||||
return pwEntry
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
@@ -108,19 +160,4 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
||||
internal var textViewTitle: TextView? = null
|
||||
internal var textViewSubTitle: TextView? = null
|
||||
}
|
||||
|
||||
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
|
||||
return database.searchEntries(constraint.toString())
|
||||
}
|
||||
|
||||
fun getEntryFromPosition(position: Int): EntryVersioned? {
|
||||
var pwEntry: EntryVersioned? = null
|
||||
|
||||
val cursor = this.cursor
|
||||
if (cursor.moveToFirst() && cursor.move(position)) {
|
||||
pwEntry = database.getEntryFrom(cursor)
|
||||
}
|
||||
return pwEntry
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.app
|
||||
|
||||
@@ -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.app.database
|
||||
|
||||
import android.os.AsyncTask
|
||||
|
||||
@@ -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.app.database
|
||||
|
||||
import androidx.room.Database
|
||||
|
||||
@@ -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.app.database
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@@ -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.app.database
|
||||
|
||||
import androidx.room.*
|
||||
|
||||
@@ -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.app.database
|
||||
|
||||
import android.os.Parcel
|
||||
|
||||
@@ -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.app.database
|
||||
@@ -112,6 +112,14 @@ class FileDatabaseHistoryAction(applicationContext: Context) {
|
||||
).execute()
|
||||
}
|
||||
|
||||
fun deleteKeyFileByDatabaseUri(databaseUri: Uri) {
|
||||
ActionDatabaseAsyncTask(
|
||||
{
|
||||
databaseFileHistoryDao.deleteKeyFileByDatabaseUri(databaseUri.toString())
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
|
||||
fun deleteAllKeyFiles() {
|
||||
ActionDatabaseAsyncTask(
|
||||
{
|
||||
|
||||
@@ -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.app.database
|
||||
|
||||
import androidx.room.*
|
||||
@@ -19,6 +38,9 @@ interface FileDatabaseHistoryDao {
|
||||
@Delete
|
||||
fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int
|
||||
|
||||
@Query("UPDATE file_database_history SET keyfile_uri=null WHERE database_uri = :databaseUriString")
|
||||
fun deleteKeyFileByDatabaseUri(databaseUriString: String)
|
||||
|
||||
@Query("UPDATE file_database_history SET keyfile_uri=null")
|
||||
fun deleteAllKeyFiles()
|
||||
|
||||
|
||||
@@ -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.app.database
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
|
||||
@@ -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.autofill
|
||||
@@ -26,15 +26,22 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.FillResponse
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.Log
|
||||
import android.view.autofill.AutofillManager
|
||||
import android.view.autofill.AutofillValue
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import java.util.*
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@@ -43,6 +50,7 @@ object AutofillHelper {
|
||||
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
|
||||
|
||||
private const val ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
|
||||
const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
|
||||
|
||||
fun retrieveAssistStructure(intent: Intent?): AssistStructure? {
|
||||
intent?.let {
|
||||
@@ -56,27 +64,44 @@ object AutofillHelper {
|
||||
return String.format("%s (%s)", entryInfo.title, entryInfo.username)
|
||||
if (entryInfo.title.isNotEmpty())
|
||||
return entryInfo.title
|
||||
if (entryInfo.username.isNotEmpty())
|
||||
return entryInfo.username
|
||||
if (entryInfo.url.isNotEmpty())
|
||||
return entryInfo.url
|
||||
if (entryInfo.username.isNotEmpty())
|
||||
return entryInfo.username
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun buildDataset(context: Context,
|
||||
internal fun addHeader(responseBuilder: FillResponse.Builder,
|
||||
packageName: String,
|
||||
webDomain: String?,
|
||||
applicationId: String?) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (webDomain != null) {
|
||||
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_web_domain).apply {
|
||||
setTextViewText(R.id.autofill_web_domain_text, webDomain)
|
||||
})
|
||||
} else if (applicationId != null) {
|
||||
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_app_id).apply {
|
||||
setTextViewText(R.id.autofill_app_id_text, applicationId)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun buildDataset(context: Context,
|
||||
entryInfo: EntryInfo,
|
||||
struct: StructureParser.Result): Dataset? {
|
||||
val title = makeEntryTitle(entryInfo)
|
||||
val views = newRemoteViews(context.packageName, title)
|
||||
val views = newRemoteViews(context, title, entryInfo.icon)
|
||||
val builder = Dataset.Builder(views)
|
||||
builder.setId(entryInfo.id)
|
||||
|
||||
struct.password.forEach { id -> builder.setValue(id, AutofillValue.forText(entryInfo.password)) }
|
||||
|
||||
val ids = ArrayList(struct.username)
|
||||
if (entryInfo.username.contains("@") || struct.username.isEmpty())
|
||||
ids.addAll(struct.email)
|
||||
ids.forEach { id -> builder.setValue(id, AutofillValue.forText(entryInfo.username)) }
|
||||
struct.usernameId?.let { usernameId ->
|
||||
builder.setValue(usernameId, AutofillValue.forText(entryInfo.username))
|
||||
}
|
||||
struct.passwordId?.let { password ->
|
||||
builder.setValue(password, AutofillValue.forText(entryInfo.password))
|
||||
}
|
||||
|
||||
return try {
|
||||
builder.build()
|
||||
@@ -87,9 +112,16 @@ object AutofillHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to hit when right key is selected
|
||||
* Build the Autofill response for one entry
|
||||
*/
|
||||
fun buildResponseWhenEntrySelected(activity: Activity, entryInfo: EntryInfo) {
|
||||
fun buildResponse(activity: Activity, entryInfo: EntryInfo) {
|
||||
buildResponse(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Autofill response for many entry
|
||||
*/
|
||||
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) {
|
||||
var setResultOk = false
|
||||
activity.intent?.extras?.let { extras ->
|
||||
if (extras.containsKey(ASSIST_STRUCTURE)) {
|
||||
@@ -97,8 +129,9 @@ object AutofillHelper {
|
||||
StructureParser(structure).parse()?.let { result ->
|
||||
// New Response
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
val dataset = buildDataset(activity, entryInfo, result)
|
||||
responseBuilder.addDataset(dataset)
|
||||
entriesInfo.forEach {
|
||||
responseBuilder.addDataset(buildDataset(activity, it, result))
|
||||
}
|
||||
val mReplyIntent = Intent()
|
||||
Log.d(activity.javaClass.name, "Successed Autofill auth.")
|
||||
mReplyIntent.putExtra(
|
||||
@@ -116,12 +149,48 @@ object AutofillHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to perform actions if item is found or not after an auto search in [database]
|
||||
*/
|
||||
fun checkAutoSearchInfo(context: Context,
|
||||
database: Database,
|
||||
searchInfo: SearchInfo?,
|
||||
onItemsFound: (items: List<EntryInfo>) -> Unit,
|
||||
onItemNotFound: () -> Unit,
|
||||
onDatabaseClosed: () -> Unit) {
|
||||
if (database.loaded && TimeoutHelper.checkTime(context)) {
|
||||
var searchWithoutUI = false
|
||||
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
|
||||
&& searchInfo != null) {
|
||||
// If search provide results
|
||||
database.createVirtualGroupFromSearch(searchInfo, SearchHelper.MAX_SEARCH_ENTRY)?.let { searchGroup ->
|
||||
if (searchGroup.getNumberOfChildEntries() > 0) {
|
||||
searchWithoutUI = true
|
||||
onItemsFound.invoke(
|
||||
searchGroup.getChildEntriesInfo(database))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!searchWithoutUI) {
|
||||
onItemNotFound.invoke()
|
||||
}
|
||||
} else {
|
||||
onDatabaseClosed.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to start an activity with an Autofill for result
|
||||
*/
|
||||
fun startActivityForAutofillResult(activity: Activity, intent: Intent, assistStructure: AssistStructure) {
|
||||
fun startActivityForAutofillResult(activity: Activity,
|
||||
intent: Intent,
|
||||
assistStructure: AssistStructure,
|
||||
searchInfo: SearchInfo?) {
|
||||
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent)
|
||||
intent.putExtra(ASSIST_STRUCTURE, assistStructure)
|
||||
searchInfo?.let {
|
||||
intent.putExtra(KEY_SEARCH_INFO, it)
|
||||
}
|
||||
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
|
||||
}
|
||||
|
||||
@@ -140,9 +209,18 @@ object AutofillHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private fun newRemoteViews(packageName: String, remoteViewsText: String): RemoteViews {
|
||||
val presentation = RemoteViews(packageName, R.layout.item_autofill_service)
|
||||
presentation.setTextViewText(R.id.text, remoteViewsText)
|
||||
private fun newRemoteViews(context: Context,
|
||||
remoteViewsText: String,
|
||||
remoteViewsIcon: IconImage? = null): RemoteViews {
|
||||
val presentation = RemoteViews(context.packageName, R.layout.item_autofill_entry)
|
||||
presentation.setTextViewText(R.id.autofill_entry_text, remoteViewsText)
|
||||
if (remoteViewsIcon != null) {
|
||||
presentation.assignDatabaseIcon(context,
|
||||
R.id.autofill_entry_icon,
|
||||
Database.getInstance().drawFactory,
|
||||
remoteViewsIcon,
|
||||
ContextCompat.getColor(context, R.color.green))
|
||||
}
|
||||
return presentation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.autofill
|
||||
@@ -31,8 +31,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class AutofillLauncherActivity : AppCompatActivity() {
|
||||
@@ -41,11 +40,31 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
||||
val assistStructure = AutofillHelper.retrieveAssistStructure(intent)
|
||||
if (assistStructure != null) {
|
||||
if (Database.getInstance().loaded && TimeoutHelper.checkTime(this))
|
||||
GroupActivity.launchForAutofillResult(this, assistStructure, PreferencesUtil.enableReadOnlyDatabase(this))
|
||||
else {
|
||||
FileDatabaseSelectActivity.launchForAutofillResult(this, assistStructure)
|
||||
// Build search param
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
|
||||
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
|
||||
}
|
||||
// If database is open
|
||||
AutofillHelper.checkAutoSearchInfo(this,
|
||||
Database.getInstance(),
|
||||
searchInfo,
|
||||
{ items ->
|
||||
// Items found
|
||||
AutofillHelper.buildResponse(this, items)
|
||||
finish()
|
||||
},
|
||||
{
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForAutofillResult(this,
|
||||
assistStructure)
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
FileDatabaseSelectActivity.launchForAutofillResult(this,
|
||||
assistStructure, searchInfo)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
@@ -61,10 +80,20 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun getAuthIntentSenderForResponse(context: Context): IntentSender {
|
||||
val intent = Intent(context, AutofillLauncherActivity::class.java)
|
||||
private const val KEY_SEARCH_APPLICATION_ID = "KEY_SEARCH_APPLICATION_ID"
|
||||
private const val KEY_SEARCH_DOMAIN = "KEY_SEARCH_DOMAIN"
|
||||
|
||||
fun getAuthIntentSenderForResponse(context: Context,
|
||||
searchInfo: SearchInfo? = null): IntentSender {
|
||||
return PendingIntent.getActivity(context, 0,
|
||||
intent, PendingIntent.FLAG_CANCEL_CURRENT).intentSender
|
||||
// Doesn't work with Parcelable (don't know why?)
|
||||
Intent(context, AutofillLauncherActivity::class.java).apply {
|
||||
searchInfo?.let {
|
||||
putExtra(KEY_SEARCH_APPLICATION_ID, it.applicationId)
|
||||
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
|
||||
}
|
||||
},
|
||||
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.autofill
|
||||
@@ -22,31 +22,78 @@ package com.kunzisoft.keepass.autofill
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import android.service.autofill.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class KeeAutofillService : AutofillService() {
|
||||
|
||||
override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal,
|
||||
override fun onFillRequest(request: FillRequest,
|
||||
cancellationSignal: CancellationSignal,
|
||||
callback: FillCallback) {
|
||||
val fillContexts = request.fillContexts
|
||||
val latestStructure = fillContexts[fillContexts.size - 1].structure
|
||||
|
||||
cancellationSignal.setOnCancelListener { Log.e(TAG, "Cancel autofill not implemented in this sample.") }
|
||||
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
|
||||
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
// Check user's settings for authenticating Responses and Datasets.
|
||||
val parseResult = StructureParser(latestStructure).parse()
|
||||
parseResult?.allAutofillIds()?.let { autofillIds ->
|
||||
if (listOf(*autofillIds).isNotEmpty()) {
|
||||
StructureParser(latestStructure).parse()?.let { parseResult ->
|
||||
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = parseResult.domain
|
||||
}
|
||||
|
||||
AutofillHelper.checkAutoSearchInfo(this,
|
||||
Database.getInstance(),
|
||||
searchInfo,
|
||||
{ items ->
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
AutofillHelper.addHeader(responseBuilder, packageName,
|
||||
parseResult.domain, parseResult.applicationId)
|
||||
items.forEach {
|
||||
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
|
||||
}
|
||||
callback.onSuccess(responseBuilder.build())
|
||||
},
|
||||
{
|
||||
// Show UI if no search result
|
||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||
},
|
||||
{
|
||||
// Show UI if database not open
|
||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
|
||||
searchInfo: SearchInfo,
|
||||
callback: FillCallback) {
|
||||
parseResult.allAutofillIds().let { autofillIds ->
|
||||
if (autofillIds.isNotEmpty()) {
|
||||
// If the entire Autofill Response is authenticated, AuthActivity is used
|
||||
// to generate Response.
|
||||
val sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this)
|
||||
val presentation = RemoteViews(packageName, R.layout.item_autofill_service_unlock)
|
||||
responseBuilder.setAuthentication(autofillIds, sender, presentation)
|
||||
val sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this,
|
||||
searchInfo)
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
val remoteViewsUnlock: RemoteViews = if (!parseResult.domain.isNullOrEmpty()) {
|
||||
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
|
||||
setTextViewText(R.id.autofill_web_domain_text, parseResult.domain)
|
||||
}
|
||||
} else if (!parseResult.applicationId.isNullOrEmpty()) {
|
||||
RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply {
|
||||
setTextViewText(R.id.autofill_app_id_text, parseResult.applicationId)
|
||||
}
|
||||
} else {
|
||||
RemoteViews(packageName, R.layout.item_autofill_unlock)
|
||||
}
|
||||
responseBuilder.setAuthentication(autofillIds, sender, remoteViewsUnlock)
|
||||
callback.onSuccess(responseBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.autofill
|
||||
|
||||
import android.app.assist.AssistStructure
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.text.InputType
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.autofill.AutofillId
|
||||
@@ -37,72 +37,196 @@ internal class StructureParser(private val structure: AssistStructure) {
|
||||
private var usernameCandidate: AutofillId? = null
|
||||
|
||||
fun parse(): Result? {
|
||||
try {
|
||||
result = Result()
|
||||
result?.apply {
|
||||
usernameCandidate = null
|
||||
for (i in 0 until structure.windowNodeCount) {
|
||||
mainLoop@ for (i in 0 until structure.windowNodeCount) {
|
||||
val windowNode = structure.getWindowNodeAt(i)
|
||||
title.add(windowNode.title)
|
||||
windowNode.rootViewNode.webDomain?.let {
|
||||
webDomain.add(it)
|
||||
}
|
||||
parseViewNode(windowNode.rootViewNode)
|
||||
applicationId = windowNode.title.toString().split("/")[0]
|
||||
Log.d(TAG, "Autofill applicationId: $applicationId")
|
||||
|
||||
if (parseViewNode(windowNode.rootViewNode))
|
||||
break@mainLoop
|
||||
}
|
||||
// If not explicit username field found, add the field just before password field.
|
||||
if (username.isEmpty() && email.isEmpty()
|
||||
&& password.isNotEmpty() && usernameCandidate != null)
|
||||
username.add(usernameCandidate!!)
|
||||
if (usernameId == null && passwordId != null && usernameCandidate != null)
|
||||
usernameId = usernameCandidate
|
||||
}
|
||||
|
||||
return result
|
||||
// Return the result only if password field is retrieved
|
||||
return if (result?.usernameId != null
|
||||
&& result?.passwordId != null)
|
||||
result
|
||||
else
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseViewNode(node: AssistStructure.ViewNode) {
|
||||
private fun parseViewNode(node: AssistStructure.ViewNode): Boolean {
|
||||
// Get the domain of a web app
|
||||
node.webDomain?.let {
|
||||
result?.domain = it
|
||||
Log.d(TAG, "Autofill domain: $it")
|
||||
}
|
||||
|
||||
// Only parse visible nodes
|
||||
if (node.visibility == View.VISIBLE) {
|
||||
if (node.autofillId != null
|
||||
&& node.autofillType == View.AUTOFILL_TYPE_TEXT) {
|
||||
// Parse methods
|
||||
val hints = node.autofillHints
|
||||
val autofillId = node.autofillId
|
||||
if (autofillId != null) {
|
||||
if (hints != null && hints.isNotEmpty()) {
|
||||
when {
|
||||
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_USERNAME == it } -> result?.username?.add(autofillId)
|
||||
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_EMAIL_ADDRESS == it } -> result?.email?.add(autofillId)
|
||||
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_PASSWORD == it } -> result?.password?.add(autofillId)
|
||||
else -> Log.d(TAG, "unsupported hints")
|
||||
if (parseNodeByAutofillHint(node))
|
||||
return true
|
||||
} else if (parseNodeByHtmlAttributes(node))
|
||||
return true
|
||||
else if (parseNodeByAndroidInput(node))
|
||||
return true
|
||||
}
|
||||
} else if (node.autofillType == View.AUTOFILL_TYPE_TEXT) {
|
||||
val inputType = node.inputType
|
||||
when {
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS > 0 -> result?.email?.add(autofillId)
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD > 0 -> result?.password?.add(autofillId)
|
||||
result?.password?.isEmpty() == true -> usernameCandidate = autofillId
|
||||
// Recursive method to process each node
|
||||
for (i in 0 until node.childCount) {
|
||||
if (parseViewNode(node.getChildAt(i)))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for (i in 0 until node.childCount)
|
||||
parseViewNode(node.getChildAt(i))
|
||||
private fun parseNodeByAutofillHint(node: AssistStructure.ViewNode): Boolean {
|
||||
val autofillId = node.autofillId
|
||||
node.autofillHints?.forEach {
|
||||
when {
|
||||
it.equals(View.AUTOFILL_HINT_USERNAME, true)
|
||||
|| it.equals(View.AUTOFILL_HINT_EMAIL_ADDRESS, true)
|
||||
|| it.equals(View.AUTOFILL_HINT_PHONE, true)
|
||||
|| it.equals("usernameOrEmail", true)-> {
|
||||
result?.usernameId = autofillId
|
||||
Log.d(TAG, "Autofill username hint")
|
||||
}
|
||||
it.equals(View.AUTOFILL_HINT_PASSWORD, true)
|
||||
|| it.contains("password", true) -> {
|
||||
result?.passwordId = autofillId
|
||||
Log.d(TAG, "Autofill password hint")
|
||||
return true
|
||||
}
|
||||
// Ignore autocomplete="off"
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
||||
it.equals("off", true) ||
|
||||
it.equals("on", true) -> {
|
||||
Log.d(TAG, "Autofill web hint")
|
||||
return parseNodeByHtmlAttributes(node)
|
||||
}
|
||||
else -> Log.d(TAG, "Autofill unsupported hint $it")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun parseNodeByHtmlAttributes(node: AssistStructure.ViewNode): Boolean {
|
||||
val autofillId = node.autofillId
|
||||
val nodHtml = node.htmlInfo
|
||||
when (nodHtml?.tag?.toLowerCase(Locale.ENGLISH)) {
|
||||
"input" -> {
|
||||
nodHtml.attributes?.forEach { pairAttribute ->
|
||||
when (pairAttribute.first.toLowerCase(Locale.ENGLISH)) {
|
||||
"type" -> {
|
||||
when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) {
|
||||
"tel", "email" -> {
|
||||
result?.usernameId = autofillId
|
||||
Log.d(TAG, "Autofill username web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
||||
}
|
||||
"text" -> {
|
||||
usernameCandidate = autofillId
|
||||
Log.d(TAG, "Autofill username candidate web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
||||
}
|
||||
"password" -> {
|
||||
result?.passwordId = autofillId
|
||||
Log.d(TAG, "Autofill password web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun parseNodeByAndroidInput(node: AssistStructure.ViewNode): Boolean {
|
||||
val autofillId = node.autofillId
|
||||
val inputType = node.inputType
|
||||
if (inputType and InputType.TYPE_CLASS_TEXT != 0) {
|
||||
when {
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != 0 -> {
|
||||
result?.usernameId = autofillId
|
||||
Log.d(TAG, "Autofill username android type: $inputType")
|
||||
}
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_NORMAL != 0 ||
|
||||
inputType and InputType.TYPE_NUMBER_VARIATION_NORMAL != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_PERSON_NAME != 0 -> {
|
||||
usernameCandidate = autofillId
|
||||
Log.d(TAG, "Autofill username candidate android type: $inputType")
|
||||
}
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != 0 ||
|
||||
inputType and InputType.TYPE_NUMBER_VARIATION_PASSWORD != 0 -> {
|
||||
result?.passwordId = autofillId
|
||||
Log.d(TAG, "Autofill password android type: $inputType")
|
||||
return true
|
||||
}
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_FILTER != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_PHONETIC != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_URI != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != 0 ||
|
||||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != 0 -> {
|
||||
// Type not used
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Autofill unknown android type: $inputType")
|
||||
usernameCandidate = autofillId
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
internal class Result {
|
||||
val title: MutableList<CharSequence>
|
||||
val webDomain: MutableList<String>
|
||||
val username: MutableList<AutofillId>
|
||||
val email: MutableList<AutofillId>
|
||||
val password: MutableList<AutofillId>
|
||||
var applicationId: String? = null
|
||||
var domain: String? = null
|
||||
set(value) {
|
||||
if (field == null)
|
||||
field = value
|
||||
}
|
||||
|
||||
init {
|
||||
title = ArrayList()
|
||||
webDomain = ArrayList()
|
||||
username = ArrayList()
|
||||
email = ArrayList()
|
||||
password = ArrayList()
|
||||
var usernameId: AutofillId? = null
|
||||
set(value) {
|
||||
if (field == null)
|
||||
field = value
|
||||
}
|
||||
|
||||
var passwordId: AutofillId? = null
|
||||
set(value) {
|
||||
if (field == null)
|
||||
field = value
|
||||
}
|
||||
|
||||
fun allAutofillIds(): Array<AutofillId> {
|
||||
val all = ArrayList<AutofillId>()
|
||||
all.addAll(username)
|
||||
all.addAll(email)
|
||||
all.addAll(password)
|
||||
usernameId?.let {
|
||||
all.add(it)
|
||||
}
|
||||
passwordId?.let {
|
||||
all.add(it)
|
||||
}
|
||||
return all.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.backup
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
/*
|
||||
* 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.biometric
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricConstants
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
@@ -19,12 +41,12 @@ import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
var databaseFileUri: Uri,
|
||||
var advancedUnlockInfoView: AdvancedUnlockInfoView?,
|
||||
var checkboxPasswordView: CompoundButton?,
|
||||
var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
|
||||
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
|
||||
private var checkboxPasswordView: CompoundButton?,
|
||||
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
|
||||
var passwordView: TextView?,
|
||||
var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
|
||||
var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
|
||||
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
|
||||
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
|
||||
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
|
||||
|
||||
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
|
||||
@@ -34,61 +56,54 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
|
||||
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
|
||||
|
||||
// fingerprint related code here
|
||||
fun initBiometric() {
|
||||
|
||||
// Check if fingerprint well init (be called the first time the fingerprint is configured
|
||||
// and the activity still active)
|
||||
if (biometricUnlockDatabaseHelper == null || !biometricUnlockDatabaseHelper!!.isBiometricInitialized) {
|
||||
|
||||
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context, this)
|
||||
// callback for fingerprint findings
|
||||
biometricUnlockDatabaseHelper?.setAuthenticationCallback(biometricCallback)
|
||||
}
|
||||
|
||||
init {
|
||||
// Add a check listener to change fingerprint mode
|
||||
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
|
||||
|
||||
checkBiometricAvailability()
|
||||
|
||||
// Add old listener to enable the button, only be call here because of onCheckedChange bug
|
||||
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
|
||||
}
|
||||
|
||||
checkBiometricAvailability()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
/**
|
||||
* Check biometric availability and change the current mode depending of device's state
|
||||
*/
|
||||
fun checkBiometricAvailability() {
|
||||
|
||||
// fingerprint not supported (by API level or hardware) so keep option hidden
|
||||
// biometric not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate()
|
||||
|
||||
if (!PreferencesUtil.isBiometricUnlockEnable(context)
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||
|
||||
toggleMode(Mode.UNAVAILABLE)
|
||||
|
||||
} else {
|
||||
|
||||
// fingerprint is available but not configured, show icon but in disabled state with some information
|
||||
// biometric is available but not configured, show icon but in disabled state with some information
|
||||
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
|
||||
|
||||
toggleMode(Mode.NOT_CONFIGURED)
|
||||
|
||||
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
|
||||
} else {
|
||||
// Check if fingerprint well init (be called the first time the fingerprint is configured
|
||||
// and the activity still active)
|
||||
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
|
||||
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
|
||||
// callback for fingerprint findings
|
||||
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
|
||||
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
|
||||
}
|
||||
// Recheck to change the mode
|
||||
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
|
||||
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
} else {
|
||||
if (checkboxPasswordView?.isChecked == true) {
|
||||
// listen for encryption
|
||||
toggleMode(Mode.STORE)
|
||||
toggleMode(Mode.STORE_CREDENTIAL)
|
||||
} else {
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
|
||||
|
||||
// fingerprint available but no stored password found yet for this DB so show info don't listen
|
||||
toggleMode( if (it) {
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
|
||||
// biometric available but no stored password found yet for this DB so show info don't listen
|
||||
toggleMode( if (containsCipher) {
|
||||
// listen for decryption
|
||||
Mode.OPEN
|
||||
Mode.EXTRACT_CREDENTIAL
|
||||
} else {
|
||||
// wait for typing
|
||||
Mode.WAIT_CREDENTIAL
|
||||
@@ -98,6 +113,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleMode(newBiometricMode: Mode) {
|
||||
if (newBiometricMode != biometricMode) {
|
||||
@@ -106,7 +122,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
}
|
||||
}
|
||||
|
||||
private val biometricCallback = object : BiometricPrompt.AuthenticationCallback () {
|
||||
private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
|
||||
|
||||
override fun onAuthenticationError(
|
||||
errorCode: Int,
|
||||
@@ -127,19 +143,15 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
context.runOnUiThread {
|
||||
when (biometricMode) {
|
||||
Mode.UNAVAILABLE -> {
|
||||
}
|
||||
Mode.PAUSE -> {
|
||||
}
|
||||
Mode.NOT_CONFIGURED -> {
|
||||
}
|
||||
Mode.WAIT_CREDENTIAL -> {
|
||||
}
|
||||
Mode.STORE -> {
|
||||
Mode.UNAVAILABLE -> {}
|
||||
Mode.BIOMETRIC_NOT_CONFIGURED -> {}
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> {}
|
||||
Mode.WAIT_CREDENTIAL -> {}
|
||||
Mode.STORE_CREDENTIAL -> {
|
||||
// newly store the entered password in encrypted way
|
||||
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
|
||||
}
|
||||
Mode.OPEN -> {
|
||||
Mode.EXTRACT_CREDENTIAL -> {
|
||||
// retrieve the encrypted value from preferences
|
||||
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
|
||||
it?.encryptedValue?.let { value ->
|
||||
@@ -155,11 +167,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
private fun initNotAvailable() {
|
||||
showFingerPrintViews(false)
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
||||
}
|
||||
|
||||
private fun initPause() {
|
||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||
}
|
||||
|
||||
private fun initNotConfigured() {
|
||||
@@ -167,7 +175,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
setAdvancedUnlockedTitleView(R.string.configure_biometric)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initKeyManagerNotAvailable() {
|
||||
showFingerPrintViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initWaitData() {
|
||||
@@ -175,7 +195,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
biometricAuthenticationCallback.onAuthenticationError(
|
||||
BiometricConstants.ERROR_UNABLE_TO_PROCESS
|
||||
, context.getString(R.string.credential_before_click_biometric_button))
|
||||
}
|
||||
}
|
||||
|
||||
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
|
||||
cryptoObject: BiometricPrompt.CryptoObject,
|
||||
promptInfo: BiometricPrompt.PromptInfo) {
|
||||
context.runOnUiThread {
|
||||
biometricPrompt?.authenticate(promptInfo, cryptoObject)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initEncryptData() {
|
||||
@@ -188,9 +220,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
cryptoObject?.let { crypto ->
|
||||
// Set listener to open the biometric dialog and save credential
|
||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
context.runOnUiThread {
|
||||
biometricPrompt?.authenticate(promptInfo, crypto)
|
||||
}
|
||||
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,17 +241,13 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
cryptoObject?.let { crypto ->
|
||||
// Set listener to open the biometric dialog and check credential
|
||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
context.runOnUiThread {
|
||||
biometricPrompt?.authenticate(promptInfo, crypto)
|
||||
}
|
||||
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
|
||||
}
|
||||
|
||||
// Auto open the biometric prompt
|
||||
if (isBiometricPromptAutoOpenEnable) {
|
||||
isBiometricPromptAutoOpenEnable = false
|
||||
context.runOnUiThread {
|
||||
biometricPrompt?.authenticate(promptInfo, crypto)
|
||||
}
|
||||
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,28 +261,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
fun initBiometricMode() {
|
||||
when (biometricMode) {
|
||||
Mode.UNAVAILABLE -> initNotAvailable()
|
||||
Mode.PAUSE -> initPause()
|
||||
Mode.NOT_CONFIGURED -> initNotConfigured()
|
||||
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
|
||||
Mode.WAIT_CREDENTIAL -> initWaitData()
|
||||
Mode.STORE -> initEncryptData()
|
||||
Mode.OPEN -> initDecryptData()
|
||||
Mode.STORE_CREDENTIAL -> initEncryptData()
|
||||
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||
}
|
||||
// Show fingerprint key deletion
|
||||
context.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
biometricMode = Mode.PAUSE
|
||||
initBiometricMode()
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
// Restore the checked listener
|
||||
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
|
||||
|
||||
biometricMode = Mode.UNAVAILABLE
|
||||
initBiometricMode()
|
||||
biometricUnlockDatabaseHelper = null
|
||||
}
|
||||
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
@@ -265,7 +282,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
if (!addBiometricMenuInProgress) {
|
||||
addBiometricMenuInProgress = true
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
|
||||
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.NOT_CONFIGURED)
|
||||
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.BIOMETRIC_NOT_CONFIGURED)
|
||||
&& it) {
|
||||
menuInflater.inflate(R.menu.advanced_unlock, menu)
|
||||
addBiometricMenuInProgress = false
|
||||
@@ -277,7 +294,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
fun deleteEntryKey() {
|
||||
biometricUnlockDatabaseHelper?.deleteEntryKey()
|
||||
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri)
|
||||
biometricMode = Mode.NOT_CONFIGURED
|
||||
biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED
|
||||
checkBiometricAvailability()
|
||||
}
|
||||
|
||||
@@ -296,8 +313,9 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
}
|
||||
|
||||
override fun onBiometricException(e: Exception) {
|
||||
if (e.localizedMessage != null)
|
||||
setAdvancedUnlockedMessageView(e.localizedMessage)
|
||||
e.localizedMessage?.let {
|
||||
setAdvancedUnlockedMessageView(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFingerPrintViews(show: Boolean) {
|
||||
@@ -323,7 +341,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
UNAVAILABLE, PAUSE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN
|
||||
UNAVAILABLE, BIOMETRIC_NOT_CONFIGURED, KEY_MANAGER_UNAVAILABLE, WAIT_CREDENTIAL, STORE_CREDENTIAL, EXTRACT_CREDENTIAL
|
||||
}
|
||||
|
||||
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.biometric
|
||||
@@ -42,8 +42,7 @@ import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
private val biometricUnlockCallback: BiometricUnlockCallback) {
|
||||
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
||||
|
||||
private var biometricPrompt: BiometricPrompt? = null
|
||||
|
||||
@@ -53,12 +52,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
private var keyguardManager: KeyguardManager? = null
|
||||
private var cryptoObject: BiometricPrompt.CryptoObject? = null
|
||||
|
||||
private var isBiometricInit = false
|
||||
private var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
|
||||
private var isKeyManagerInit = false
|
||||
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
|
||||
var biometricUnlockCallback: BiometricUnlockCallback? = null
|
||||
|
||||
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
|
||||
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
|
||||
setConfirmationRequired(true)
|
||||
// TODO device credential
|
||||
/*
|
||||
if (keyguardManager?.isDeviceSecure == true)
|
||||
@@ -70,7 +71,8 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
|
||||
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
|
||||
setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
||||
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
||||
setConfirmationRequired(false)
|
||||
// TODO device credential
|
||||
/*
|
||||
if (keyguardManager?.isDeviceSecure == true)
|
||||
@@ -80,20 +82,20 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
setNegativeButtonText(context.getString(android.R.string.cancel))
|
||||
}.build()
|
||||
|
||||
val isBiometricInitialized: Boolean
|
||||
val isKeyManagerInitialized: Boolean
|
||||
get() {
|
||||
if (!isBiometricInit) {
|
||||
biometricUnlockCallback.onBiometricException(Exception("Biometric not initialized"))
|
||||
if (!isKeyManagerInit) {
|
||||
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
|
||||
}
|
||||
return isBiometricInit
|
||||
return isKeyManagerInit
|
||||
}
|
||||
|
||||
init {
|
||||
if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
// really not much to do when no fingerprint support found
|
||||
isBiometricInit = false
|
||||
isKeyManagerInit = false
|
||||
} else {
|
||||
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
|
||||
|
||||
try {
|
||||
this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE)
|
||||
@@ -103,17 +105,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
+ BIOMETRIC_BLOCKS_MODES + "/"
|
||||
+ BIOMETRIC_ENCRYPTION_PADDING)
|
||||
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
|
||||
isBiometricInit = true
|
||||
isKeyManagerInit = (keyStore != null
|
||||
&& keyGenerator != null
|
||||
&& cipher != null)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize the keystore", e)
|
||||
isBiometricInit = false
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
isKeyManagerInit = false
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSecretKey(): SecretKey? {
|
||||
if (!isBiometricInitialized) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
@@ -139,14 +143,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to create a key in keystore", e)
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
|
||||
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey?
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve the key in keystore", e)
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -155,7 +159,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
: (biometricPrompt: BiometricPrompt?,
|
||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
||||
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
|
||||
if (!isBiometricInitialized) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -168,19 +172,18 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
||||
biometricUnlockCallback.onInvalidKeyException(unrecoverableKeyException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
|
||||
biometricUnlockCallback.onInvalidKeyException(invalidKeyException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", e)
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun encryptData(value: String) {
|
||||
if (!isBiometricInitialized) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -190,21 +193,20 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
// passes updated iv spec on to callback so this can be stored for decryption
|
||||
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
|
||||
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
|
||||
biometricUnlockCallback.handleEncryptedResult(encryptedBase64, ivSpecValue)
|
||||
biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to encrypt data", e)
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
|
||||
: (biometricPrompt: BiometricPrompt?,
|
||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
||||
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
|
||||
if (!isBiometricInitialized) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -224,32 +226,30 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
deleteEntryKey()
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
|
||||
biometricUnlockCallback.onInvalidKeyException(invalidKeyException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", e)
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun decryptData(encryptedValue: String) {
|
||||
if (!isBiometricInitialized) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// actual decryption here
|
||||
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
|
||||
cipher?.doFinal(encrypted)?.let { decrypted ->
|
||||
biometricUnlockCallback.handleDecryptedResult(String(decrypted))
|
||||
biometricUnlockCallback?.handleDecryptedResult(String(decrypted))
|
||||
}
|
||||
} catch (badPaddingException: BadPaddingException) {
|
||||
Log.e(TAG, "Unable to decrypt data", badPaddingException)
|
||||
biometricUnlockCallback.onInvalidKeyException(badPaddingException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(badPaddingException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to decrypt data", e)
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun deleteEntryKey() {
|
||||
@@ -258,14 +258,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to delete entry key in keystore", e)
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun setAuthenticationCallback(authenticationCallback: BiometricPrompt.AuthenticationCallback) {
|
||||
this.authenticationCallback = authenticationCallback
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun initBiometricPrompt() {
|
||||
if (biometricPrompt == null) {
|
||||
@@ -299,22 +295,24 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
||||
* Remove entry key in keystore
|
||||
*/
|
||||
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
|
||||
biometricUnlockCallback: BiometricUnlockErrorCallback) {
|
||||
val fingerPrintHelper = BiometricUnlockDatabaseHelper(context, object : BiometricUnlockCallback {
|
||||
biometricCallback: BiometricUnlockErrorCallback) {
|
||||
BiometricUnlockDatabaseHelper(context).apply {
|
||||
biometricUnlockCallback = object : BiometricUnlockCallback {
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
|
||||
|
||||
override fun handleDecryptedResult(decryptedValue: String) {}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
biometricUnlockCallback.onInvalidKeyException(e)
|
||||
biometricCallback.onInvalidKeyException(e)
|
||||
}
|
||||
|
||||
override fun onBiometricException(e: Exception) {
|
||||
biometricUnlockCallback.onBiometricException(e)
|
||||
biometricCallback.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
deleteEntryKey()
|
||||
}
|
||||
})
|
||||
fingerPrintHelper.deleteEntryKey()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +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.biometric
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Animatable2
|
||||
import android.graphics.drawable.AnimatedVectorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.widget.ImageView
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
|
||||
|
||||
private val scanFingerprint: AnimatedVectorDrawable =
|
||||
context.getDrawable(R.drawable.scan_fingerprint) as AnimatedVectorDrawable
|
||||
private val scanFingerprint: AnimatedVectorDrawableCompat? =
|
||||
AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint)
|
||||
|
||||
init {
|
||||
imageView.setImageDrawable(scanFingerprint)
|
||||
}
|
||||
|
||||
private var animationCallback = object : Animatable2.AnimationCallback() {
|
||||
private var animationCallback = object : Animatable2Compat.AnimationCallback() {
|
||||
override fun onAnimationEnd(drawable: Drawable) {
|
||||
if (!scanFingerprint.isRunning)
|
||||
scanFingerprint.start()
|
||||
imageView.post {
|
||||
scanFingerprint?.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startScan() {
|
||||
scanFingerprint.registerAnimationCallback(animationCallback)
|
||||
scanFingerprint?.registerAnimationCallback(animationCallback)
|
||||
|
||||
if (!scanFingerprint.isRunning)
|
||||
scanFingerprint.start()
|
||||
if (scanFingerprint?.isRunning != true)
|
||||
scanFingerprint?.start()
|
||||
}
|
||||
|
||||
fun stopScan() {
|
||||
scanFingerprint.unregisterAnimationCallback(animationCallback)
|
||||
scanFingerprint?.unregisterAnimationCallback(animationCallback)
|
||||
|
||||
if (scanFingerprint.isRunning)
|
||||
if (scanFingerprint?.isRunning == true)
|
||||
scanFingerprint.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.crypto
|
||||
|
||||
@@ -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.crypto
|
||||
|
||||
@@ -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.crypto
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
/*
|
||||
* 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.crypto
|
||||
|
||||
import com.kunzisoft.keepass.stream.LEDataOutputStream
|
||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||
|
||||
import com.kunzisoft.keepass.stream.longTo8Bytes
|
||||
import java.io.IOException
|
||||
import java.security.DigestOutputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.Arrays
|
||||
|
||||
import java.util.*
|
||||
import javax.crypto.Mac
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -60,7 +58,7 @@ object CryptoUtil {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
val pbR = LEDataOutputStream.writeLongBuf(r)
|
||||
val pbR = longTo8Bytes(r)
|
||||
val part = hmac.doFinal(pbR)
|
||||
|
||||
val copy = min(cbOut - pos, part.size)
|
||||
|
||||
@@ -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.crypto;
|
||||
|
||||
@@ -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.crypto
|
||||
|
||||
@@ -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.crypto
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
/*
|
||||
* 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.crypto.engine
|
||||
|
||||
|
||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.utils.Types
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
@@ -41,13 +41,28 @@ class AesEngine : CipherEngine() {
|
||||
return cipher
|
||||
}
|
||||
|
||||
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
|
||||
return PwEncryptionAlgorithm.AESRijndael
|
||||
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
|
||||
return EncryptionAlgorithm.AESRijndael
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
||||
byteArrayOf(0x31.toByte(), 0xC1.toByte(), 0xF2.toByte(), 0xE6.toByte(), 0xBF.toByte(), 0x71.toByte(), 0x43.toByte(), 0x50.toByte(), 0xBE.toByte(), 0x58.toByte(), 0x05.toByte(), 0x21.toByte(), 0x6A.toByte(), 0xFC.toByte(), 0x5A.toByte(), 0xFF.toByte()))
|
||||
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||
byteArrayOf(0x31.toByte(),
|
||||
0xC1.toByte(),
|
||||
0xF2.toByte(),
|
||||
0xE6.toByte(),
|
||||
0xBF.toByte(),
|
||||
0x71.toByte(),
|
||||
0x43.toByte(),
|
||||
0x50.toByte(),
|
||||
0xBE.toByte(),
|
||||
0x58.toByte(),
|
||||
0x05.toByte(),
|
||||
0x21.toByte(),
|
||||
0x6A.toByte(),
|
||||
0xFC.toByte(),
|
||||
0x5A.toByte(),
|
||||
0xFF.toByte()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/*
|
||||
* 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.crypto.engine
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.utils.Types
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
@@ -44,13 +44,28 @@ class ChaCha20Engine : CipherEngine() {
|
||||
return cipher
|
||||
}
|
||||
|
||||
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
|
||||
return PwEncryptionAlgorithm.ChaCha20
|
||||
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
|
||||
return EncryptionAlgorithm.ChaCha20
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
||||
byteArrayOf(0xD6.toByte(), 0x03.toByte(), 0x8A.toByte(), 0x2B.toByte(), 0x8B.toByte(), 0x6F.toByte(), 0x4C.toByte(), 0xB5.toByte(), 0xA5.toByte(), 0x24.toByte(), 0x33.toByte(), 0x9A.toByte(), 0x31.toByte(), 0xDB.toByte(), 0xB5.toByte(), 0x9A.toByte()))
|
||||
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||
byteArrayOf(0xD6.toByte(),
|
||||
0x03.toByte(),
|
||||
0x8A.toByte(),
|
||||
0x2B.toByte(),
|
||||
0x8B.toByte(),
|
||||
0x6F.toByte(),
|
||||
0x4C.toByte(),
|
||||
0xB5.toByte(),
|
||||
0xA5.toByte(),
|
||||
0x24.toByte(),
|
||||
0x33.toByte(),
|
||||
0x9A.toByte(),
|
||||
0x31.toByte(),
|
||||
0xDB.toByte(),
|
||||
0xB5.toByte(),
|
||||
0x9A.toByte()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.crypto.engine
|
||||
|
||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
@@ -46,6 +46,6 @@ abstract class CipherEngine {
|
||||
return getCipher(opmode, key, IV, false)
|
||||
}
|
||||
|
||||
abstract fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm
|
||||
abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
|
||||
|
||||
}
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
/*
|
||||
* 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.crypto.engine
|
||||
|
||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.utils.Types
|
||||
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.UUID
|
||||
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
@@ -47,13 +45,28 @@ class TwofishEngine : CipherEngine() {
|
||||
return cipher
|
||||
}
|
||||
|
||||
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
|
||||
return PwEncryptionAlgorithm.Twofish
|
||||
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
|
||||
return EncryptionAlgorithm.Twofish
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
||||
byteArrayOf(0xAD.toByte(), 0x68.toByte(), 0xF2.toByte(), 0x9F.toByte(), 0x57.toByte(), 0x6F.toByte(), 0x4B.toByte(), 0xB9.toByte(), 0xA3.toByte(), 0x6A.toByte(), 0xD4.toByte(), 0x7A.toByte(), 0xF9.toByte(), 0x65.toByte(), 0x34.toByte(), 0x6C.toByte()))
|
||||
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||
byteArrayOf(0xAD.toByte(),
|
||||
0x68.toByte(),
|
||||
0xF2.toByte(),
|
||||
0x9F.toByte(),
|
||||
0x57.toByte(),
|
||||
0x6F.toByte(),
|
||||
0x4B.toByte(),
|
||||
0xB9.toByte(),
|
||||
0xA3.toByte(),
|
||||
0x6A.toByte(),
|
||||
0xD4.toByte(),
|
||||
0x7A.toByte(),
|
||||
0xF9.toByte(),
|
||||
0x65.toByte(),
|
||||
0x34.toByte(),
|
||||
0x6C.toByte()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.crypto.finalkey;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.crypto.finalkey;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.crypto.finalkey;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.crypto.finalkey;
|
||||
|
||||
@@ -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.crypto.keyDerivation
|
||||
@@ -23,7 +23,7 @@ import android.content.res.Resources
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.crypto.CryptoUtil
|
||||
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
|
||||
import com.kunzisoft.keepass.utils.Types
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
@@ -32,7 +32,7 @@ class AesKdf internal constructor() : KdfEngine() {
|
||||
|
||||
override val defaultParameters: KdfParameters
|
||||
get() {
|
||||
return KdfParameters(uuid).apply {
|
||||
return KdfParameters(uuid!!).apply {
|
||||
setParamUUID()
|
||||
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() {
|
||||
|
||||
private const val DEFAULT_ROUNDS = 6000
|
||||
|
||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
||||
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||
byteArrayOf(0xC9.toByte(),
|
||||
0xD9.toByte(),
|
||||
0xF3.toByte(),
|
||||
|
||||
@@ -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.crypto.keyDerivation
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.Types
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
@@ -30,7 +30,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
|
||||
override val defaultParameters: KdfParameters
|
||||
get() {
|
||||
val p = KdfParameters(uuid)
|
||||
val p = KdfParameters(uuid!!)
|
||||
|
||||
p.setParamUUID()
|
||||
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
|
||||
@@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
|
||||
companion object {
|
||||
|
||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
||||
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||
byteArrayOf(0xEF.toByte(),
|
||||
0x63.toByte(),
|
||||
0x6D.toByte(),
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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.crypto.keyDerivation;
|
||||
|
||||
@@ -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.crypto.keyDerivation
|
||||
|
||||
import com.kunzisoft.keepass.database.ObjectNameResource
|
||||
import com.kunzisoft.keepass.utils.ObjectNameResource
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
|
||||
@@ -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.crypto.keyDerivation
|
||||
|
||||
@@ -1,75 +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.crypto.keyDerivation;
|
||||
|
||||
import com.kunzisoft.keepass.utils.VariantDictionary;
|
||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
||||
import com.kunzisoft.keepass.stream.LEDataOutputStream;
|
||||
import com.kunzisoft.keepass.utils.Types;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KdfParameters extends VariantDictionary {
|
||||
|
||||
private UUID kdfUUID;
|
||||
|
||||
private static final String ParamUUID = "$UUID";
|
||||
|
||||
KdfParameters(UUID uuid) {
|
||||
kdfUUID = uuid;
|
||||
}
|
||||
|
||||
public UUID getUUID() {
|
||||
return kdfUUID;
|
||||
}
|
||||
|
||||
protected void setParamUUID() {
|
||||
setByteArray(ParamUUID, Types.UUIDtoBytes(kdfUUID));
|
||||
}
|
||||
|
||||
public static KdfParameters deserialize(byte[] data) throws IOException {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(data);
|
||||
LEDataInputStream lis = new LEDataInputStream(bis);
|
||||
|
||||
VariantDictionary d = VariantDictionary.deserialize(lis);
|
||||
if (d == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UUID uuid = Types.bytestoUUID(d.getByteArray(ParamUUID));
|
||||
|
||||
KdfParameters kdfP = new KdfParameters(uuid);
|
||||
kdfP.copyTo(d);
|
||||
return kdfP;
|
||||
}
|
||||
|
||||
public static byte[] serialize(KdfParameters kdf) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
LEDataOutputStream los = new LEDataOutputStream(bos);
|
||||
|
||||
KdfParameters.serialize(kdf, los);
|
||||
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.crypto.keyDerivation
|
||||
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import com.kunzisoft.keepass.stream.uuidTo16Bytes
|
||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
|
||||
|
||||
fun setParamUUID() {
|
||||
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PARAM_UUID = "\$UUID"
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun deserialize(data: ByteArray): KdfParameters? {
|
||||
val bis = ByteArrayInputStream(data)
|
||||
val lis = LittleEndianDataInputStream(bis)
|
||||
|
||||
val d = deserialize(lis) ?: return null
|
||||
|
||||
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
|
||||
|
||||
val kdfP = KdfParameters(uuid)
|
||||
kdfP.copyTo(d)
|
||||
return kdfP
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun serialize(kdf: KdfParameters): ByteArray {
|
||||
val bos = ByteArrayOutputStream()
|
||||
val los = LittleEndianDataOutputStream(bos)
|
||||
|
||||
serialize(kdf, los)
|
||||
|
||||
return bos.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.kunzisoft.keepass.database
|
||||
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
||||
import com.kunzisoft.keepass.database.element.NodeVersioned
|
||||
import com.kunzisoft.keepass.database.element.Type
|
||||
import java.util.*
|
||||
|
||||
enum class SortNodeEnum {
|
||||
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
|
||||
|
||||
fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator<NodeVersioned> {
|
||||
return when (this) {
|
||||
DB -> NodeNaturalComparator(ascending, groupsBefore, false) // Force false because natural order contains recycle bin
|
||||
TITLE -> NodeTitleComparator(ascending, groupsBefore, recycleBinBottom)
|
||||
USERNAME -> NodeUsernameComparator(ascending, groupsBefore, recycleBinBottom)
|
||||
CREATION_TIME -> NodeCreationComparator(ascending, groupsBefore, recycleBinBottom)
|
||||
LAST_MODIFY_TIME -> NodeLastModificationComparator(ascending, groupsBefore, recycleBinBottom)
|
||||
LAST_ACCESS_TIME -> NodeLastAccessComparator(ascending, groupsBefore, recycleBinBottom)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<NodeVersioned> {
|
||||
|
||||
abstract fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int
|
||||
|
||||
private fun specificOrderOrHashIfEquals(object1: NodeVersioned, object2: NodeVersioned): Int {
|
||||
val specificOrderComp = compareBySpecificOrder(object1, object2)
|
||||
|
||||
return if (specificOrderComp == 0) {
|
||||
object1.hashCode() - object2.hashCode()
|
||||
} else if (!ascending) -specificOrderComp else specificOrderComp // If descending, revert
|
||||
}
|
||||
|
||||
override fun compare(object1: NodeVersioned,object2: NodeVersioned): Int {
|
||||
if (object1 == object2)
|
||||
return 0
|
||||
|
||||
if (object1.type == Type.GROUP) {
|
||||
return if (object2.type == Type.GROUP) {
|
||||
// RecycleBin at end of groups
|
||||
if (recycleBinBottom) {
|
||||
if (Database.getInstance().recycleBin == object1)
|
||||
return 1
|
||||
if (Database.getInstance().recycleBin == object2)
|
||||
return -1
|
||||
}
|
||||
|
||||
specificOrderOrHashIfEquals(object1, object2)
|
||||
} else if (object2.type == Type.ENTRY) {
|
||||
if (groupsBefore)
|
||||
-1
|
||||
else
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
} else if (object1.type == Type.ENTRY) {
|
||||
return if (object2.type == Type.ENTRY) {
|
||||
specificOrderOrHashIfEquals(object1, object2)
|
||||
} else if (object2.type == Type.GROUP) {
|
||||
if (groupsBefore)
|
||||
1
|
||||
else
|
||||
-1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
// Type not known
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of node by natural database placement
|
||||
*/
|
||||
class NodeNaturalComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
|
||||
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
|
||||
|
||||
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
|
||||
return object1.nodePositionInParent.compareTo(object2.nodePositionInParent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Node by Title
|
||||
*/
|
||||
class NodeTitleComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
|
||||
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
|
||||
|
||||
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
|
||||
return object1.title.compareTo(object2.title, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Node by Username, Groups by title
|
||||
*/
|
||||
class NodeUsernameComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
|
||||
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
|
||||
|
||||
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
|
||||
if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) {
|
||||
// To get username if it's a ref
|
||||
return (object1 as EntryVersioned).getEntryInfo(Database.getInstance()).username
|
||||
.compareTo((object2 as EntryVersioned).getEntryInfo(Database.getInstance()).username,
|
||||
ignoreCase = true)
|
||||
}
|
||||
return NodeTitleComparator(ascending, groupsBefore, recycleBinBottom).compare(object1, object2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of node by creation
|
||||
*/
|
||||
class NodeCreationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
|
||||
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
|
||||
|
||||
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
|
||||
return object1.creationTime.date
|
||||
.compareTo(object2.creationTime.date)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of node by last modification
|
||||
*/
|
||||
class NodeLastModificationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
|
||||
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
|
||||
|
||||
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
|
||||
return object1.lastModificationTime.date
|
||||
.compareTo(object2.lastModificationTime.date)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of node by last access
|
||||
*/
|
||||
class NodeLastAccessComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
|
||||
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
|
||||
|
||||
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
|
||||
return object1.lastAccessTime.date
|
||||
.compareTo(object2.lastAccessTime.date)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,43 @@
|
||||
/*
|
||||
* 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.database.action
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
||||
open class AssignPasswordInDatabaseRunnable (
|
||||
context: Context,
|
||||
database: Database,
|
||||
protected val mDatabaseUri: Uri,
|
||||
withMasterPassword: Boolean,
|
||||
masterPassword: String?,
|
||||
withKeyFile: Boolean,
|
||||
keyFile: Uri?,
|
||||
save: Boolean,
|
||||
actionRunnable: ActionRunnable? = null)
|
||||
: SaveDatabaseRunnable(context, database, save, actionRunnable) {
|
||||
keyFile: Uri?)
|
||||
: SaveDatabaseRunnable(context, database, true) {
|
||||
|
||||
private var mMasterPassword: String? = null
|
||||
private var mKeyFile: Uri? = null
|
||||
protected var mKeyFile: Uri? = null
|
||||
|
||||
private var mBackupKey: ByteArray? = null
|
||||
|
||||
@@ -48,7 +48,7 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
||||
this.mKeyFile = keyFile
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
override fun onStartRun() {
|
||||
// Set key
|
||||
try {
|
||||
// TODO move master key methods
|
||||
@@ -57,17 +57,24 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
||||
|
||||
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFile)
|
||||
database.retrieveMasterKey(mMasterPassword, uriInputStream)
|
||||
|
||||
// To save the database
|
||||
super.run()
|
||||
finishRun(true)
|
||||
} catch (e: Exception) {
|
||||
erase(mBackupKey)
|
||||
finishRun(false, e.message)
|
||||
}
|
||||
setError(e)
|
||||
}
|
||||
|
||||
override fun onFinishRun(result: Result) {
|
||||
super.onStartRun()
|
||||
}
|
||||
|
||||
override fun onFinishRun() {
|
||||
super.onFinishRun()
|
||||
|
||||
// Erase the biometric
|
||||
CipherDatabaseAction.getInstance(context)
|
||||
.deleteByDatabaseUri(mDatabaseUri)
|
||||
// Erase the register keyfile
|
||||
FileDatabaseHistoryAction.getInstance(context)
|
||||
.deleteKeyFileByDatabaseUri(mDatabaseUri)
|
||||
|
||||
if (!result.isSuccess) {
|
||||
// Erase the current master key
|
||||
erase(database.masterKey)
|
||||
@@ -75,8 +82,6 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
||||
database.masterKey = it
|
||||
}
|
||||
}
|
||||
|
||||
super.onFinishRun(result)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,58 +1,70 @@
|
||||
/*
|
||||
* 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.database.action
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
class CreateDatabaseRunnable(context: Context,
|
||||
private val mDatabaseUri: Uri,
|
||||
private val mDatabase: Database,
|
||||
databaseUri: Uri,
|
||||
private val databaseName: String,
|
||||
private val rootName: String,
|
||||
withMasterPassword: Boolean,
|
||||
masterPassword: String?,
|
||||
withKeyFile: Boolean,
|
||||
keyFile: Uri?,
|
||||
save: Boolean,
|
||||
actionRunnable: ActionRunnable? = null)
|
||||
: AssignPasswordInDatabaseRunnable(context, mDatabase, withMasterPassword, masterPassword, withKeyFile, keyFile, save, actionRunnable) {
|
||||
keyFile: Uri?)
|
||||
: AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) {
|
||||
|
||||
override fun run() {
|
||||
override fun onStartRun() {
|
||||
try {
|
||||
// Create new database record
|
||||
mDatabase.apply {
|
||||
createData(mDatabaseUri)
|
||||
createData(mDatabaseUri, databaseName, rootName)
|
||||
// Set Database state
|
||||
loaded = true
|
||||
// Commit changes
|
||||
super.run()
|
||||
}
|
||||
|
||||
finishRun(true)
|
||||
} catch (e: Exception) {
|
||||
|
||||
mDatabase.closeAndClear()
|
||||
finishRun(false, e.message)
|
||||
}
|
||||
setError(e)
|
||||
}
|
||||
|
||||
override fun onFinishRun(result: Result) {}
|
||||
super.onStartRun()
|
||||
}
|
||||
|
||||
override fun onFinishRun() {
|
||||
super.onFinishRun()
|
||||
|
||||
if (result.isSuccess) {
|
||||
// Add database to recent files
|
||||
if (PreferencesUtil.rememberDatabaseLocations(context)) {
|
||||
FileDatabaseHistoryAction.getInstance(context.applicationContext)
|
||||
.addOrUpdateDatabaseUri(mDatabaseUri,
|
||||
if (PreferencesUtil.rememberKeyFileLocations(context)) mKeyFile else null)
|
||||
}
|
||||
} else {
|
||||
Log.e("CreateDatabaseRunnable", "Unable to create the database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.database.action
|
||||
@@ -25,7 +25,9 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
|
||||
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
@@ -40,16 +42,18 @@ class LoadDatabaseRunnable(private val context: Context,
|
||||
private val mOmitBackup: Boolean,
|
||||
private val mFixDuplicateUUID: Boolean,
|
||||
private val progressTaskUpdater: ProgressTaskUpdater?,
|
||||
actionFinishRunnable: ActionRunnable?)
|
||||
: ActionRunnable(actionFinishRunnable, executeNestedActionIfResultFalse = true) {
|
||||
private val mDuplicateUuidAction: ((Result) -> Unit)?)
|
||||
: ActionRunnable() {
|
||||
|
||||
private val cacheDirectory = context.applicationContext.filesDir
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
override fun onStartRun() {
|
||||
// Clear before we load
|
||||
mDatabase.closeAndClear(cacheDirectory)
|
||||
}
|
||||
|
||||
override fun onActionRun() {
|
||||
try {
|
||||
mDatabase.loadData(mUri, mPass, mKey,
|
||||
mReadonly,
|
||||
context.contentResolver,
|
||||
@@ -57,35 +61,37 @@ class LoadDatabaseRunnable(private val context: Context,
|
||||
mOmitBackup,
|
||||
mFixDuplicateUUID,
|
||||
progressTaskUpdater)
|
||||
|
||||
// Save keyFile in app database
|
||||
val rememberKeyFile = PreferencesUtil.rememberKeyFiles(context)
|
||||
if (rememberKeyFile) {
|
||||
var keyUri = mKey
|
||||
if (!rememberKeyFile) {
|
||||
keyUri = null
|
||||
}
|
||||
catch (e: DuplicateUuidDatabaseException) {
|
||||
mDuplicateUuidAction?.invoke(result)
|
||||
setError(e)
|
||||
}
|
||||
catch (e: LoadDatabaseException) {
|
||||
setError(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishRun() {
|
||||
if (result.isSuccess) {
|
||||
// Save keyFile in app database
|
||||
if (PreferencesUtil.rememberDatabaseLocations(context)) {
|
||||
FileDatabaseHistoryAction.getInstance(context)
|
||||
.addOrUpdateDatabaseUri(mUri, keyUri)
|
||||
.addOrUpdateDatabaseUri(mUri,
|
||||
if (PreferencesUtil.rememberKeyFileLocations(context)) mKey else null)
|
||||
}
|
||||
|
||||
// Register the biometric
|
||||
mCipherEntity?.let { cipherDatabaseEntity ->
|
||||
CipherDatabaseAction.getInstance(context)
|
||||
.addOrUpdateCipherDatabase(cipherDatabaseEntity) {
|
||||
finishRun(true)
|
||||
}
|
||||
} ?: run {
|
||||
finishRun(true)
|
||||
}
|
||||
}
|
||||
catch (e: LoadDatabaseException) {
|
||||
finishRun(false, e)
|
||||
}
|
||||
.addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
|
||||
}
|
||||
|
||||
override fun onFinishRun(result: Result) {
|
||||
if (!result.isSuccess) {
|
||||
// Register the current time to init the lock timer
|
||||
PreferencesUtil.saveCurrentTime(context)
|
||||
|
||||
// Start the opening notification
|
||||
DatabaseOpenNotificationService.start(context)
|
||||
} else {
|
||||
mDatabase.closeAndClear(cacheDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.database.action
|
||||
|
||||
import android.content.*
|
||||
@@ -8,30 +27,40 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COLOR_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COMPRESSION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DESCRIPTION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ENCRYPTION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ITERATIONS_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_NAME_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_PARALLELISM_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
|
||||
@@ -44,10 +73,10 @@ import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ProgressDialogThread(private val activity: FragmentActivity) {
|
||||
|
||||
class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
var onActionFinish: (actionTask: String,
|
||||
result: ActionRunnable.Result) -> Unit) {
|
||||
var onActionFinish: ((actionTask: String,
|
||||
result: ActionRunnable.Result) -> Unit)? = null
|
||||
|
||||
private var intentDatabaseTask = Intent(activity, DatabaseTaskNotificationService::class.java)
|
||||
|
||||
@@ -59,19 +88,35 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
||||
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||
TimeoutHelper.temporarilyDisableTimeout()
|
||||
// Stop the opening notification
|
||||
DatabaseOpenNotificationService.stop(activity)
|
||||
startOrUpdateDialog(titleId, messageId, warningId)
|
||||
|
||||
}
|
||||
|
||||
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||
TimeoutHelper.temporarilyDisableTimeout()
|
||||
// Stop the opening notification
|
||||
DatabaseOpenNotificationService.stop(activity)
|
||||
startOrUpdateDialog(titleId, messageId, warningId)
|
||||
}
|
||||
|
||||
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
|
||||
onActionFinish.invoke(actionTask, result)
|
||||
onActionFinish?.invoke(actionTask, result)
|
||||
// Remove the progress task
|
||||
ProgressTaskDialogFragment.stop(activity)
|
||||
TimeoutHelper.releaseTemporarilyDisableTimeoutAndLockIfTimeout(activity)
|
||||
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
||||
|
||||
val inTime = if (activity is LockingActivity) {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeout(activity)
|
||||
} else {
|
||||
TimeoutHelper.checkTime(activity)
|
||||
}
|
||||
// Start the opening notification if in time
|
||||
// (databaseOpenService is open manually in Action Open Task)
|
||||
if (actionTask != ACTION_DATABASE_LOAD_TASK && inTime) {
|
||||
DatabaseOpenNotificationService.start(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +144,7 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
if (serviceConnection == null) {
|
||||
serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder).apply {
|
||||
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
|
||||
addActionTaskListener(actionTaskListener)
|
||||
getService().checkAction()
|
||||
}
|
||||
@@ -172,7 +217,11 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
|
||||
unBindService()
|
||||
|
||||
try {
|
||||
activity.unregisterReceiver(databaseTaskBroadcastReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// If receiver not register, do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -228,12 +277,14 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
, ACTION_DATABASE_LOAD_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseAssignPassword(masterPasswordChecked: Boolean,
|
||||
fun startDatabaseAssignPassword(databaseUri: Uri,
|
||||
masterPasswordChecked: Boolean,
|
||||
masterPassword: String?,
|
||||
keyFileChecked: Boolean,
|
||||
keyFile: Uri?) {
|
||||
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||
putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
|
||||
putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
|
||||
putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked)
|
||||
@@ -248,8 +299,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
----
|
||||
*/
|
||||
|
||||
fun startDatabaseCreateGroup(newGroup: GroupVersioned,
|
||||
parent: GroupVersioned,
|
||||
fun startDatabaseCreateGroup(newGroup: Group,
|
||||
parent: Group,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.GROUP_KEY, newGroup)
|
||||
@@ -259,8 +310,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
, ACTION_DATABASE_CREATE_GROUP_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseUpdateGroup(oldGroup: GroupVersioned,
|
||||
groupToUpdate: GroupVersioned,
|
||||
fun startDatabaseUpdateGroup(oldGroup: Group,
|
||||
groupToUpdate: Group,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.GROUP_ID_KEY, oldGroup.nodeId)
|
||||
@@ -270,8 +321,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
, ACTION_DATABASE_UPDATE_GROUP_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseCreateEntry(newEntry: EntryVersioned,
|
||||
parent: GroupVersioned,
|
||||
fun startDatabaseCreateEntry(newEntry: Entry,
|
||||
parent: Group,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, newEntry)
|
||||
@@ -281,8 +332,8 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
, ACTION_DATABASE_CREATE_ENTRY_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseUpdateEntry(oldEntry: EntryVersioned,
|
||||
entryToUpdate: EntryVersioned,
|
||||
fun startDatabaseUpdateEntry(oldEntry: Entry,
|
||||
entryToUpdate: Entry,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, oldEntry.nodeId)
|
||||
@@ -293,20 +344,20 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
}
|
||||
|
||||
private fun startDatabaseActionListNodes(actionTask: String,
|
||||
nodesPaste: List<NodeVersioned>,
|
||||
newParent: GroupVersioned?,
|
||||
nodesPaste: List<Node>,
|
||||
newParent: Group?,
|
||||
save: Boolean) {
|
||||
val groupsIdToCopy = ArrayList<PwNodeId<*>>()
|
||||
val entriesIdToCopy = ArrayList<PwNodeId<UUID>>()
|
||||
val groupsIdToCopy = ArrayList<NodeId<*>>()
|
||||
val entriesIdToCopy = ArrayList<NodeId<UUID>>()
|
||||
nodesPaste.forEach { nodeVersioned ->
|
||||
when (nodeVersioned.type) {
|
||||
Type.GROUP -> {
|
||||
(nodeVersioned as GroupVersioned).nodeId?.let { groupId ->
|
||||
(nodeVersioned as Group).nodeId?.let { groupId ->
|
||||
groupsIdToCopy.add(groupId)
|
||||
}
|
||||
}
|
||||
Type.ENTRY -> {
|
||||
entriesIdToCopy.add((nodeVersioned as EntryVersioned).nodeId)
|
||||
entriesIdToCopy.add((nodeVersioned as Entry).nodeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,23 +374,51 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
, actionTask)
|
||||
}
|
||||
|
||||
fun startDatabaseCopyNodes(nodesToCopy: List<NodeVersioned>,
|
||||
newParent: GroupVersioned,
|
||||
fun startDatabaseCopyNodes(nodesToCopy: List<Node>,
|
||||
newParent: Group,
|
||||
save: Boolean) {
|
||||
startDatabaseActionListNodes(ACTION_DATABASE_COPY_NODES_TASK, nodesToCopy, newParent, save)
|
||||
}
|
||||
|
||||
fun startDatabaseMoveNodes(nodesToMove: List<NodeVersioned>,
|
||||
newParent: GroupVersioned,
|
||||
fun startDatabaseMoveNodes(nodesToMove: List<Node>,
|
||||
newParent: Group,
|
||||
save: Boolean) {
|
||||
startDatabaseActionListNodes(ACTION_DATABASE_MOVE_NODES_TASK, nodesToMove, newParent, save)
|
||||
}
|
||||
|
||||
fun startDatabaseDeleteNodes(nodesToDelete: List<NodeVersioned>,
|
||||
fun startDatabaseDeleteNodes(nodesToDelete: List<Node>,
|
||||
save: Boolean) {
|
||||
startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save)
|
||||
}
|
||||
|
||||
/*
|
||||
-----------------
|
||||
Entry History Settings
|
||||
-----------------
|
||||
*/
|
||||
|
||||
fun startDatabaseRestoreEntryHistory(mainEntry: Entry,
|
||||
entryHistoryPosition: Int,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
|
||||
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
|
||||
}
|
||||
|
||||
fun startDatabaseDeleteEntryHistory(mainEntry: Entry,
|
||||
entryHistoryPosition: Int,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
|
||||
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_DELETE_ENTRY_HISTORY)
|
||||
}
|
||||
|
||||
/*
|
||||
-----------------
|
||||
Main Settings
|
||||
@@ -347,66 +426,80 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
*/
|
||||
|
||||
fun startDatabaseSaveName(oldName: String,
|
||||
newName: String) {
|
||||
newName: String,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldName)
|
||||
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_NAME_TASK)
|
||||
, ACTION_DATABASE_UPDATE_NAME_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveDescription(oldDescription: String,
|
||||
newDescription: String) {
|
||||
newDescription: String,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDescription)
|
||||
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_DESCRIPTION_TASK)
|
||||
, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String,
|
||||
newDefaultUsername: String) {
|
||||
newDefaultUsername: String,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDefaultUsername)
|
||||
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK)
|
||||
, ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveColor(oldColor: String,
|
||||
newColor: String) {
|
||||
newColor: String,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldColor)
|
||||
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_COLOR_TASK)
|
||||
, ACTION_DATABASE_UPDATE_COLOR_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveCompression(oldCompression: PwCompressionAlgorithm,
|
||||
newCompression: PwCompressionAlgorithm) {
|
||||
fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm,
|
||||
newCompression: CompressionAlgorithm,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldCompression)
|
||||
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_COMPRESSION_TASK)
|
||||
, ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
|
||||
newMaxHistoryItems: Int) {
|
||||
newMaxHistoryItems: Int,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistoryItems)
|
||||
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK)
|
||||
, ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long,
|
||||
newMaxHistorySize: Long) {
|
||||
newMaxHistorySize: Long,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistorySize)
|
||||
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK)
|
||||
, ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -415,48 +508,68 @@ class ProgressDialogThread(private val activity: FragmentActivity,
|
||||
-------------------
|
||||
*/
|
||||
|
||||
fun startDatabaseSaveEncryption(oldEncryption: PwEncryptionAlgorithm,
|
||||
newEncryption: PwEncryptionAlgorithm) {
|
||||
fun startDatabaseSaveEncryption(oldEncryption: EncryptionAlgorithm,
|
||||
newEncryption: EncryptionAlgorithm,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldEncryption)
|
||||
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_ENCRYPTION_TASK)
|
||||
, ACTION_DATABASE_UPDATE_ENCRYPTION_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine,
|
||||
newKeyDerivation: KdfEngine) {
|
||||
newKeyDerivation: KdfEngine,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldKeyDerivation)
|
||||
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK)
|
||||
, ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveIterations(oldIterations: Long,
|
||||
newIterations: Long) {
|
||||
newIterations: Long,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldIterations)
|
||||
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_ITERATIONS_TASK)
|
||||
, ACTION_DATABASE_UPDATE_ITERATIONS_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long,
|
||||
newMemoryUsage: Long) {
|
||||
newMemoryUsage: Long,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMemoryUsage)
|
||||
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK)
|
||||
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveParallelism(oldParallelism: Int,
|
||||
newParallelism: Int) {
|
||||
newParallelism: Int,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
|
||||
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE_PARALLELISM_TASK)
|
||||
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK)
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Database without parameter
|
||||
*/
|
||||
fun startDatabaseSave(save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_SAVE)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +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.database.action
|
||||
|
||||
import android.content.Context
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseException
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import java.io.IOException
|
||||
|
||||
abstract class SaveDatabaseRunnable(protected var context: Context,
|
||||
open class SaveDatabaseRunnable(protected var context: Context,
|
||||
protected var database: Database,
|
||||
protected var saveDatabase: Boolean,
|
||||
nestedAction: ActionRunnable? = null)
|
||||
: ActionRunnable(nestedAction) {
|
||||
private var saveDatabase: Boolean)
|
||||
: ActionRunnable() {
|
||||
|
||||
override fun run() {
|
||||
if (saveDatabase) {
|
||||
var mAfterSaveDatabase: ((Result) -> Unit)? = null
|
||||
|
||||
override fun onStartRun() {}
|
||||
|
||||
override fun onActionRun() {
|
||||
if (saveDatabase && result.isSuccess) {
|
||||
try {
|
||||
database.saveData(context.contentResolver)
|
||||
} catch (e: IOException) {
|
||||
finishRun(false, e.message)
|
||||
} catch (e: DatabaseOutputException) {
|
||||
finishRun(false, e.message)
|
||||
} catch (e: DatabaseException) {
|
||||
setError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to call super.run() in child class
|
||||
}
|
||||
|
||||
override fun onFinishRun(result: Result) {
|
||||
// Need to call super.onFinishRun(result) in child class
|
||||
}
|
||||
}
|
||||
|
||||
class SaveDatabaseActionRunnable(context: Context,
|
||||
database: Database,
|
||||
save: Boolean,
|
||||
nestedAction: ActionRunnable? = null)
|
||||
: SaveDatabaseRunnable(context, database, save, nestedAction) {
|
||||
|
||||
override fun run() {
|
||||
super.run()
|
||||
finishRun(true)
|
||||
override fun onFinishRun() {
|
||||
// Need to call super.onFinishRun() in child class
|
||||
mAfterSaveDatabase?.invoke(result)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user